diff --git a/package-lock.json b/package-lock.json
index af4fad0697..7e283f2244 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -58,6 +58,7 @@
"postcss-import": "^14.0.2",
"prettier": "^2.3.2",
"snowpack": "^3.8.2",
+ "ts-jest": "^26.5.6",
"ts-node": "^10.1.0",
"typescript": "4.2.4"
},
@@ -4789,6 +4790,18 @@
"url": "https://opencollective.com/browserslist"
}
},
+ "node_modules/bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "dependencies": {
+ "fast-json-stable-stringify": "2.x"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/bser": {
"version": "2.1.1",
"integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
@@ -16097,6 +16110,49 @@
"integrity": "sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g==",
"dev": true
},
+ "node_modules/ts-jest": {
+ "version": "26.5.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
+ "integrity": "sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==",
+ "dev": true,
+ "dependencies": {
+ "bs-logger": "0.x",
+ "buffer-from": "1.x",
+ "fast-json-stable-stringify": "2.x",
+ "jest-util": "^26.1.0",
+ "json5": "2.x",
+ "lodash": "4.x",
+ "make-error": "1.x",
+ "mkdirp": "1.x",
+ "semver": "7.x",
+ "yargs-parser": "20.x"
+ },
+ "bin": {
+ "ts-jest": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "peerDependencies": {
+ "jest": ">=26 <27",
+ "typescript": ">=3.8 <5.0"
+ }
+ },
+ "node_modules/ts-jest/node_modules/semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/ts-node": {
"version": "10.1.0",
"integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==",
@@ -20470,6 +20526,15 @@
"node-releases": "^1.1.71"
}
},
+ "bs-logger": {
+ "version": "0.2.6",
+ "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz",
+ "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==",
+ "dev": true,
+ "requires": {
+ "fast-json-stable-stringify": "2.x"
+ }
+ },
"bser": {
"version": "2.1.1",
"integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
@@ -28974,6 +29039,35 @@
"integrity": "sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g==",
"dev": true
},
+ "ts-jest": {
+ "version": "26.5.6",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.6.tgz",
+ "integrity": "sha512-rua+rCP8DxpA8b4DQD/6X2HQS8Zy/xzViVYfEs2OQu68tkCuKLV0Md8pmX55+W24uRIyAsf/BajRfxOs+R2MKA==",
+ "dev": true,
+ "requires": {
+ "bs-logger": "0.x",
+ "buffer-from": "1.x",
+ "fast-json-stable-stringify": "2.x",
+ "jest-util": "^26.1.0",
+ "json5": "2.x",
+ "lodash": "4.x",
+ "make-error": "1.x",
+ "mkdirp": "1.x",
+ "semver": "7.x",
+ "yargs-parser": "20.x"
+ },
+ "dependencies": {
+ "semver": {
+ "version": "7.3.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
+ "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
+ "dev": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ }
+ }
+ },
"ts-node": {
"version": "10.1.0",
"integrity": "sha512-6szn3+J9WyG2hE+5W8e0ruZrzyk1uFLYye6IGMBadnOzDh8aP7t8CbFpsfCiEx2+wMixAhjFt7lOZC4+l+WbEA==",
diff --git a/package.json b/package.json
index 4ec711f46a..c48c042adb 100644
--- a/package.json
+++ b/package.json
@@ -74,6 +74,7 @@
"postcss-import": "^14.0.2",
"prettier": "^2.3.2",
"snowpack": "^3.8.2",
+ "ts-jest": "^26.5.6",
"ts-node": "^10.1.0",
"typescript": "4.2.4"
},
diff --git a/src/PageHeader.tsx b/src/PageHeader.tsx
index 955550b6e6..b31259e25a 100644
--- a/src/PageHeader.tsx
+++ b/src/PageHeader.tsx
@@ -1,5 +1,3 @@
-import React, { useContext, useState } from "react";
-import { useTranslation } from "react-i18next";
import {
Avatar,
Brand,
@@ -10,14 +8,16 @@ import {
KebabToggle,
PageHeader,
PageHeaderTools,
- PageHeaderToolsItem,
PageHeaderToolsGroup,
+ PageHeaderToolsItem,
} from "@patternfly/react-core";
import { HelpIcon } from "@patternfly/react-icons";
-import { WhoAmIContext } from "./context/whoami/WhoAmI";
-import { HelpContext, HelpHeader } from "./components/help-enabler/HelpHeader";
+import React, { useState } from "react";
+import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom";
+import { HelpHeader, useHelp } from "./components/help-enabler/HelpHeader";
import { useAdminClient } from "./context/auth/AdminClient";
+import { useWhoAmI } from "./context/whoami/WhoAmI";
import environment from "./environment";
export const Header = () => {
@@ -73,7 +73,7 @@ export const Header = () => {
const HelpDropdownItem = () => {
const { t } = useTranslation();
- const { enabled, toggleHelp } = useContext(HelpContext);
+ const { enabled, toggleHelp } = useHelp();
return (
} onClick={toggleHelp}>
{enabled ? t("helpEnabled") : t("helpDisabled")}
@@ -155,7 +155,7 @@ export const Header = () => {
};
const UserDropdown = () => {
- const { whoAmI } = useContext(WhoAmIContext);
+ const { whoAmI } = useWhoAmI();
const [isDropdownOpen, setDropdownOpen] = useState(false);
const onDropdownToggle = () => {
diff --git a/src/client-scopes/add/RoleMappingForm.tsx b/src/client-scopes/add/RoleMappingForm.tsx
index 83fe51f55f..33d2ed3e9e 100644
--- a/src/client-scopes/add/RoleMappingForm.tsx
+++ b/src/client-scopes/add/RoleMappingForm.tsx
@@ -1,36 +1,34 @@
-import React, { useContext, useState } from "react";
-import { useHistory, useParams } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import { Controller, useForm } from "react-hook-form";
import {
+ ActionGroup,
+ Button,
+ Divider,
FormGroup,
PageSection,
Select,
- SelectVariant,
- TextInput,
- SelectOption,
- ActionGroup,
- Button,
SelectGroup,
+ SelectOption,
+ SelectVariant,
Split,
SplitItem,
- Divider,
+ TextInput,
ValidatedOptions,
} from "@patternfly/react-core";
-
-import type RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import type ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
import type ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
+import type RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
+import React, { useState } from "react";
+import { Controller, useForm } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+import { useHistory, useParams } from "react-router-dom";
import { useAlerts } from "../../components/alert/Alerts";
-import { RealmContext } from "../../context/realm-context/RealmContext";
-import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
-
-import { ViewHeader } from "../../components/view-header/ViewHeader";
-import { HelpItem } from "../../components/help-enabler/HelpItem";
import { FormAccess } from "../../components/form-access/FormAccess";
+import { HelpItem } from "../../components/help-enabler/HelpItem";
+import { ViewHeader } from "../../components/view-header/ViewHeader";
+import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
+import { useRealm } from "../../context/realm-context/RealmContext";
export const RoleMappingForm = () => {
- const { realm } = useContext(RealmContext);
+ const { realm } = useRealm();
const adminClient = useAdminClient();
const history = useHistory();
const { addAlert } = useAlerts();
diff --git a/src/clients/scopes/EvaluateScopes.tsx b/src/clients/scopes/EvaluateScopes.tsx
index 192195c155..02ffe207b2 100644
--- a/src/clients/scopes/EvaluateScopes.tsx
+++ b/src/clients/scopes/EvaluateScopes.tsx
@@ -1,5 +1,3 @@
-import React, { useContext, useEffect, useRef, useState } from "react";
-import { useTranslation } from "react-i18next";
import {
ClipboardCopy,
EmptyState,
@@ -25,19 +23,19 @@ import {
} from "@patternfly/react-core";
import { QuestionCircleIcon } from "@patternfly/react-icons";
import type ClientScopeRepresentation from "keycloak-admin/lib/defs/clientScopeRepresentation";
-import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
-import type RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import type ProtocolMapperRepresentation from "keycloak-admin/lib/defs/protocolMapperRepresentation";
+import type RoleRepresentation from "keycloak-admin/lib/defs/roleRepresentation";
import type { ProtocolMapperTypeRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
-
-import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
-import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
-import { RealmContext } from "../../context/realm-context/RealmContext";
-import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
+import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
+import React, { useEffect, useRef, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useHelp } from "../../components/help-enabler/HelpHeader";
import { HelpItem } from "../../components/help-enabler/HelpItem";
-
+import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
+import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
+import { useRealm } from "../../context/realm-context/RealmContext";
+import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import "./evaluate.css";
-import { HelpContext } from "../../components/help-enabler/HelpHeader";
export type EvaluateScopesProps = {
clientId: string;
@@ -114,9 +112,9 @@ const EffectiveRoles = ({
export const EvaluateScopes = ({ clientId, protocol }: EvaluateScopesProps) => {
const prefix = "openid";
const { t } = useTranslation("clients");
- const { enabled } = useContext(HelpContext);
+ const { enabled } = useHelp();
const adminClient = useAdminClient();
- const { realm } = useContext(RealmContext);
+ const { realm } = useRealm();
const mapperTypes = useServerInfo().protocolMapperTypes![protocol];
const [selectableScopes, setSelectableScopes] = useState<
diff --git a/src/components/alert/Alerts.tsx b/src/components/alert/Alerts.tsx
index 1ae67a717d..606caeb1fa 100644
--- a/src/components/alert/Alerts.tsx
+++ b/src/components/alert/Alerts.tsx
@@ -1,6 +1,7 @@
-import React, { useState, createContext, ReactNode, useContext } from "react";
-import { AlertType, AlertPanel } from "./AlertPanel";
import { AlertVariant } from "@patternfly/react-core";
+import React, { createContext, ReactNode, useState } from "react";
+import useRequiredContext from "../../utils/useRequiredContext";
+import { AlertPanel, AlertType } from "./AlertPanel";
type AlertProps = {
addAlert: (
@@ -10,11 +11,9 @@ type AlertProps = {
) => void;
};
-export const AlertContext = createContext({
- addAlert: () => {},
-});
+export const AlertContext = createContext(undefined);
-export const useAlerts = () => useContext(AlertContext);
+export const useAlerts = () => useRequiredContext(AlertContext);
export const AlertProvider = ({ children }: { children: ReactNode }) => {
const [alerts, setAlerts] = useState([]);
diff --git a/src/components/download-dialog/DownloadDialog.tsx b/src/components/download-dialog/DownloadDialog.tsx
index 02f4fcff4b..a46639c900 100644
--- a/src/components/download-dialog/DownloadDialog.tsx
+++ b/src/components/download-dialog/DownloadDialog.tsx
@@ -1,4 +1,3 @@
-import React, { useState, useContext } from "react";
import {
Alert,
AlertVariant,
@@ -13,13 +12,13 @@ import {
TextArea,
} from "@patternfly/react-core";
import FileSaver from "file-saver";
-
-import { ConfirmDialogModal } from "../confirm-dialog/ConfirmDialog";
-import { HelpItem } from "../help-enabler/HelpItem";
+import React, { useState } from "react";
import { useTranslation } from "react-i18next";
-import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
-import { HelpContext } from "../help-enabler/HelpHeader";
+import { useServerInfo } from "../../context/server-info/ServerInfoProvider";
+import { ConfirmDialogModal } from "../confirm-dialog/ConfirmDialog";
+import { useHelp } from "../help-enabler/HelpHeader";
+import { HelpItem } from "../help-enabler/HelpItem";
type DownloadDialogProps = {
id: string;
@@ -36,7 +35,7 @@ export const DownloadDialog = ({
}: DownloadDialogProps) => {
const adminClient = useAdminClient();
const { t } = useTranslation("common");
- const { enabled } = useContext(HelpContext);
+ const { enabled } = useHelp();
const serverInfo = useServerInfo();
const configFormats = serverInfo.clientInstallations![protocol];
diff --git a/src/components/help-enabler/HelpHeader.tsx b/src/components/help-enabler/HelpHeader.tsx
index d42cda08a9..d2e264dca9 100644
--- a/src/components/help-enabler/HelpHeader.tsx
+++ b/src/components/help-enabler/HelpHeader.tsx
@@ -1,4 +1,3 @@
-import React, { useState, useContext, ReactNode, createContext } from "react";
import {
Divider,
Dropdown,
@@ -9,9 +8,10 @@ import {
Switch,
TextContent,
} from "@patternfly/react-core";
+import { ExternalLinkAltIcon, HelpIcon } from "@patternfly/react-icons";
+import React, { createContext, ReactNode, useState } from "react";
import { useTranslation } from "react-i18next";
-import { HelpIcon, ExternalLinkAltIcon } from "@patternfly/react-icons";
-
+import useRequiredContext from "../../utils/useRequiredContext";
import "./help-header.css";
type HelpProps = {
@@ -23,10 +23,11 @@ type HelpContextProps = {
toggleHelp: () => void;
};
-export const HelpContext = createContext({
- enabled: true,
- toggleHelp: () => {},
-});
+export const HelpContext = createContext(
+ undefined
+);
+
+export const useHelp = () => useRequiredContext(HelpContext);
export const Help = ({ children }: HelpProps) => {
const [enabled, setHelp] = useState(true);
@@ -43,7 +44,7 @@ export const Help = ({ children }: HelpProps) => {
export const HelpHeader = () => {
const [open, setOpen] = useState(false);
- const help = useContext(HelpContext);
+ const help = useHelp();
const { t } = useTranslation();
const dropdownItems = [
diff --git a/src/components/help-enabler/HelpItem.tsx b/src/components/help-enabler/HelpItem.tsx
index e61d89a8c2..bc8383ca86 100644
--- a/src/components/help-enabler/HelpItem.tsx
+++ b/src/components/help-enabler/HelpItem.tsx
@@ -1,8 +1,8 @@
-import React, { isValidElement, ReactNode, useContext } from "react";
import { Popover } from "@patternfly/react-core";
import { HelpIcon } from "@patternfly/react-icons";
+import React, { isValidElement, ReactNode } from "react";
import { useTranslation } from "react-i18next";
-import { HelpContext } from "./HelpHeader";
+import { useHelp } from "./HelpHeader";
type HelpItemProps = {
helpText: string | ReactNode;
@@ -22,7 +22,7 @@ export const HelpItem = ({
unWrap = false,
}: HelpItemProps) => {
const { t } = useTranslation();
- const { enabled } = useContext(HelpContext);
+ const { enabled } = useHelp();
return (
<>
{enabled && (
diff --git a/src/components/realm-selector/RealmSelector.tsx b/src/components/realm-selector/RealmSelector.tsx
index a2a50117be..ae5d73d56d 100644
--- a/src/components/realm-selector/RealmSelector.tsx
+++ b/src/components/realm-selector/RealmSelector.tsx
@@ -1,31 +1,30 @@
-import React, { useState, useContext, ReactElement, useMemo } from "react";
-import { useHistory } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-
import {
- Dropdown,
- DropdownToggle,
- DropdownItem,
Button,
- Divider,
- SplitItem,
- Split,
ContextSelector,
ContextSelectorItem,
+ Divider,
+ Dropdown,
+ DropdownItem,
+ DropdownToggle,
Label,
+ Split,
+ SplitItem,
} from "@patternfly/react-core";
import { CheckIcon } from "@patternfly/react-icons";
+import React, { ReactElement, useMemo, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useHistory } from "react-router-dom";
-import { toUpperCase } from "../../util";
import { useRealm } from "../../context/realm-context/RealmContext";
-import { WhoAmIContext } from "../../context/whoami/WhoAmI";
+import { useWhoAmI } from "../../context/whoami/WhoAmI";
+import { toUpperCase } from "../../util";
import { RecentUsed } from "./recent-used";
import "./realm-selector.css";
export const RealmSelector = () => {
const { realm, setRealm, realms } = useRealm();
- const { whoAmI } = useContext(WhoAmIContext);
+ const { whoAmI } = useWhoAmI();
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const history = useHistory();
diff --git a/src/components/view-header/ViewHeader.tsx b/src/components/view-header/ViewHeader.tsx
index b18eece0e9..ce5b89210a 100644
--- a/src/components/view-header/ViewHeader.tsx
+++ b/src/components/view-header/ViewHeader.tsx
@@ -1,26 +1,26 @@
-import React, { ReactElement, ReactNode, useContext, useState } from "react";
import {
- Text,
- PageSection,
- TextContent,
+ Badge,
Divider,
+ Dropdown,
+ DropdownPosition,
+ DropdownToggle,
Level,
LevelItem,
+ PageSection,
Switch,
+ Text,
+ TextContent,
Toolbar,
ToolbarContent,
ToolbarItem,
- Badge,
- Dropdown,
- DropdownToggle,
- DropdownPosition,
} from "@patternfly/react-core";
-import { HelpContext } from "../help-enabler/HelpHeader";
+import React, { ReactElement, ReactNode, useState } from "react";
import { useTranslation } from "react-i18next";
import {
FormattedLink,
FormattedLinkProps,
} from "../external-link/FormattedLink";
+import { useHelp } from "../help-enabler/HelpHeader";
import { HelpItem } from "../help-enabler/HelpItem";
export type ViewHeaderProps = {
@@ -56,7 +56,7 @@ export const ViewHeader = ({
helpTextKey,
}: ViewHeaderProps) => {
const { t } = useTranslation();
- const { enabled } = useContext(HelpContext);
+ const { enabled } = useHelp();
const [isDropdownOpen, setDropdownOpen] = useState(false);
const [isLowerDropdownOpen, setIsLowerDropdownOpen] = useState(false);
diff --git a/src/context/access/Access.tsx b/src/context/access/Access.tsx
index 94d98bd3fa..03a395045f 100644
--- a/src/context/access/Access.tsx
+++ b/src/context/access/Access.tsx
@@ -1,25 +1,24 @@
-import React, { createContext, useContext, useEffect, useState } from "react";
import type { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation";
-
-import { RealmContext } from "../../context/realm-context/RealmContext";
-import { WhoAmIContext } from "../../context/whoami/WhoAmI";
+import React, { createContext, useEffect, useState } from "react";
+import { useRealm } from "../../context/realm-context/RealmContext";
+import { useWhoAmI } from "../../context/whoami/WhoAmI";
+import useRequiredContext from "../../utils/useRequiredContext";
type AccessContextProps = {
hasAccess: (...types: AccessType[]) => boolean;
hasSomeAccess: (...types: AccessType[]) => boolean;
};
-export const AccessContext = createContext({
- hasAccess: () => false,
- hasSomeAccess: () => false,
-});
+export const AccessContext = createContext(
+ undefined
+);
-export const useAccess = () => useContext(AccessContext);
+export const useAccess = () => useRequiredContext(AccessContext);
type AccessProviderProps = { children: React.ReactNode };
export const AccessContextProvider = ({ children }: AccessProviderProps) => {
- const { whoAmI } = useContext(WhoAmIContext);
- const { realm } = useContext(RealmContext);
+ const { whoAmI } = useWhoAmI();
+ const { realm } = useRealm();
const [access, setAccess] = useState([]);
useEffect(() => {
diff --git a/src/context/auth/AdminClient.tsx b/src/context/auth/AdminClient.tsx
index aa1e0cf192..a29f8d1776 100644
--- a/src/context/auth/AdminClient.tsx
+++ b/src/context/auth/AdminClient.tsx
@@ -1,16 +1,14 @@
-import { createContext, DependencyList, useContext, useEffect } from "react";
import axios from "axios";
-
import type KeycloakAdminClient from "keycloak-admin";
+import { createContext, DependencyList, useEffect } from "react";
import { useErrorHandler } from "react-error-boundary";
+import useRequiredContext from "../../utils/useRequiredContext";
export const AdminClient = createContext(
undefined
);
-export const useAdminClient = () => {
- return useContext(AdminClient)!;
-};
+export const useAdminClient = () => useRequiredContext(AdminClient);
/**
* Util function to only set the state when the component is still mounted.
diff --git a/src/context/realm-context/RealmContext.tsx b/src/context/realm-context/RealmContext.tsx
index 983d1f7102..0fc9dc9d0c 100644
--- a/src/context/realm-context/RealmContext.tsx
+++ b/src/context/realm-context/RealmContext.tsx
@@ -1,10 +1,10 @@
-import React, { useContext, useState } from "react";
-import _ from "lodash";
-
import type RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
+import _ from "lodash";
+import React, { useState } from "react";
import { RecentUsed } from "../../components/realm-selector/recent-used";
-import { useAdminClient, useFetch } from "../auth/AdminClient";
import environment from "../../environment";
+import useRequiredContext from "../../utils/useRequiredContext";
+import { useAdminClient, useFetch } from "../auth/AdminClient";
type RealmContextType = {
realm: string;
@@ -13,12 +13,9 @@ type RealmContextType = {
refresh: () => Promise;
};
-export const RealmContext = React.createContext({
- realm: "",
- setRealm: () => {},
- realms: [],
- refresh: () => Promise.resolve(),
-});
+export const RealmContext = React.createContext(
+ undefined
+);
type RealmContextProviderProps = { children: React.ReactNode };
@@ -70,4 +67,4 @@ export const RealmContextProvider = ({
);
};
-export const useRealm = () => useContext(RealmContext);
+export const useRealm = () => useRequiredContext(RealmContext);
diff --git a/src/context/server-info/ServerInfoProvider.tsx b/src/context/server-info/ServerInfoProvider.tsx
index 7841d0b385..82a902768a 100644
--- a/src/context/server-info/ServerInfoProvider.tsx
+++ b/src/context/server-info/ServerInfoProvider.tsx
@@ -1,15 +1,15 @@
-import React, { createContext, ReactNode, useContext } from "react";
import type { ServerInfoRepresentation } from "keycloak-admin/lib/defs/serverInfoRepesentation";
-
-import { sortProviders } from "../../util";
+import React, { createContext, ReactNode } from "react";
import { DataLoader } from "../../components/data-loader/DataLoader";
+import { sortProviders } from "../../util";
+import useRequiredContext from "../../utils/useRequiredContext";
import { useAdminClient } from "../auth/AdminClient";
-export const ServerInfoContext = createContext(
- {} as ServerInfoRepresentation
-);
+export const ServerInfoContext = createContext<
+ ServerInfoRepresentation | undefined
+>(undefined);
-export const useServerInfo = () => useContext(ServerInfoContext);
+export const useServerInfo = () => useRequiredContext(ServerInfoContext);
export const useLoginProviders = () => {
return sortProviders(useServerInfo().providers!["login-protocol"].providers);
diff --git a/src/context/whoami/WhoAmI.tsx b/src/context/whoami/WhoAmI.tsx
index ab1db70e75..27129e04e4 100644
--- a/src/context/whoami/WhoAmI.tsx
+++ b/src/context/whoami/WhoAmI.tsx
@@ -1,8 +1,8 @@
-import React, { useState } from "react";
-import i18n from "../../i18n";
-
import type WhoAmIRepresentation from "keycloak-admin/lib/defs/whoAmIRepresentation";
import type { AccessType } from "keycloak-admin/lib/defs/whoAmIRepresentation";
+import React, { useState } from "react";
+import i18n from "../../i18n";
+import useRequiredContext from "../../utils/useRequiredContext";
import { useAdminClient, useFetch } from "../auth/AdminClient";
export class WhoAmI {
@@ -44,10 +44,11 @@ type WhoAmIProps = {
whoAmI: WhoAmI;
};
-export const WhoAmIContext = React.createContext({
- refresh: () => {},
- whoAmI: new WhoAmI(),
-});
+export const WhoAmIContext = React.createContext(
+ undefined
+);
+
+export const useWhoAmI = () => useRequiredContext(WhoAmIContext);
type WhoAmIProviderProps = { children: React.ReactNode };
export const WhoAmIContextProvider = ({ children }: WhoAmIProviderProps) => {
diff --git a/src/events/AdminEvents.tsx b/src/events/AdminEvents.tsx
index 02785d6f45..f463795136 100644
--- a/src/events/AdminEvents.tsx
+++ b/src/events/AdminEvents.tsx
@@ -1,6 +1,3 @@
-import React, { ReactNode, useContext, useState } from "react";
-import { Link } from "react-router-dom";
-import { useTranslation } from "react-i18next";
import {
Button,
Modal,
@@ -8,11 +5,6 @@ import {
ToolbarItem,
Tooltip,
} from "@patternfly/react-core";
-import moment from "moment";
-
-import { useAdminClient } from "../context/auth/AdminClient";
-import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
-import { RealmContext } from "../context/realm-context/RealmContext";
import {
cellWidth,
Table,
@@ -21,7 +13,14 @@ import {
TableVariant,
} from "@patternfly/react-table";
import type AdminEventRepresentation from "keycloak-admin/lib/defs/adminEventRepresentation";
+import moment from "moment";
+import React, { ReactNode, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { Link } from "react-router-dom";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
+import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
+import { useAdminClient } from "../context/auth/AdminClient";
+import { useRealm } from "../context/realm-context/RealmContext";
type DisplayDialogProps = {
titleKey: string;
@@ -67,7 +66,7 @@ const Truncate = ({
export const AdminEvents = () => {
const { t } = useTranslation("events");
const adminClient = useAdminClient();
- const { realm } = useContext(RealmContext);
+ const { realm } = useRealm();
const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime());
diff --git a/src/events/EventsSection.tsx b/src/events/EventsSection.tsx
index 15124ade8b..18110a92ab 100644
--- a/src/events/EventsSection.tsx
+++ b/src/events/EventsSection.tsx
@@ -1,7 +1,3 @@
-import React, { useContext, useState } from "react";
-import { Link } from "react-router-dom";
-import { Trans, useTranslation } from "react-i18next";
-import moment from "moment";
import {
Button,
DescriptionList,
@@ -14,24 +10,26 @@ import {
ToolbarItem,
Tooltip,
} from "@patternfly/react-core";
-import { cellWidth, expandable } from "@patternfly/react-table";
import { CheckCircleIcon, WarningTriangleIcon } from "@patternfly/react-icons";
+import { cellWidth, expandable } from "@patternfly/react-table";
import type EventRepresentation from "keycloak-admin/lib/defs/eventRepresentation";
-
-import { useAdminClient } from "../context/auth/AdminClient";
-import { ViewHeader } from "../components/view-header/ViewHeader";
-import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
-import { RealmContext } from "../context/realm-context/RealmContext";
-import { AdminEvents } from "./AdminEvents";
-import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
+import moment from "moment";
+import React, { useState } from "react";
+import { Trans, useTranslation } from "react-i18next";
+import { Link } from "react-router-dom";
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
-
+import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
+import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
+import { ViewHeader } from "../components/view-header/ViewHeader";
+import { useAdminClient } from "../context/auth/AdminClient";
+import { useRealm } from "../context/realm-context/RealmContext";
+import { AdminEvents } from "./AdminEvents";
import "./events-section.css";
export const EventsSection = () => {
const { t } = useTranslation("events");
const adminClient = useAdminClient();
- const { realm } = useContext(RealmContext);
+ const { realm } = useRealm();
const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime());
diff --git a/src/groups/SubGroupsContext.tsx b/src/groups/SubGroupsContext.tsx
index 50a567569e..f4141b3dc8 100644
--- a/src/groups/SubGroupsContext.tsx
+++ b/src/groups/SubGroupsContext.tsx
@@ -1,5 +1,6 @@
-import React, { createContext, ReactNode, useContext, useState } from "react";
import type GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
+import React, { createContext, ReactNode, useState } from "react";
+import useRequiredContext from "../utils/useRequiredContext";
type SubGroupsProps = {
subGroups: GroupRepresentation[];
@@ -9,15 +10,7 @@ type SubGroupsProps = {
currentGroup: () => GroupRepresentation;
};
-const SubGroupContext = createContext({
- subGroups: [],
- setSubGroups: () => {},
- clear: () => {},
- remove: () => {},
- currentGroup: () => {
- return {};
- },
-});
+const SubGroupContext = createContext(undefined);
export const SubGroups = ({ children }: { children: ReactNode }) => {
const [subGroups, setSubGroups] = useState([]);
@@ -37,4 +30,4 @@ export const SubGroups = ({ children }: { children: ReactNode }) => {
);
};
-export const useSubGroups = () => useContext(SubGroupContext);
+export const useSubGroups = () => useRequiredContext(SubGroupContext);
diff --git a/src/realm-roles/UsersInRoleTab.tsx b/src/realm-roles/UsersInRoleTab.tsx
index 8c3f8a2d89..707667c2ef 100644
--- a/src/realm-roles/UsersInRoleTab.tsx
+++ b/src/realm-roles/UsersInRoleTab.tsx
@@ -1,14 +1,14 @@
-import React, { useContext } from "react";
-import { useHistory, useParams } from "react-router-dom";
-import { useTranslation } from "react-i18next";
import { Button, PageSection, Popover } from "@patternfly/react-core";
+import { QuestionCircleIcon } from "@patternfly/react-icons";
+import React from "react";
+import { useTranslation } from "react-i18next";
+import { useHistory, useParams } from "react-router-dom";
+import { useHelp } from "../components/help-enabler/HelpHeader";
import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
-import { upperCaseFormatter, emptyFormatter } from "../util";
import { useAdminClient } from "../context/auth/AdminClient";
-import { QuestionCircleIcon } from "@patternfly/react-icons";
import { useRealm } from "../context/realm-context/RealmContext";
-import { HelpContext } from "../components/help-enabler/HelpHeader";
+import { emptyFormatter, upperCaseFormatter } from "../util";
export const UsersInRoleTab = () => {
const history = useHistory();
@@ -30,7 +30,7 @@ export const UsersInRoleTab = () => {
return usersWithRole || [];
};
- const { enabled } = useContext(HelpContext);
+ const { enabled } = useHelp();
return (
<>
diff --git a/src/realm-settings/EmailTab.tsx b/src/realm-settings/EmailTab.tsx
index c0a7b66460..37724f5738 100644
--- a/src/realm-settings/EmailTab.tsx
+++ b/src/realm-settings/EmailTab.tsx
@@ -1,6 +1,3 @@
-import React, { useContext, useEffect, useState } from "react";
-import { useTranslation } from "react-i18next";
-import { Controller, useForm, useWatch } from "react-hook-form";
import {
ActionGroup,
AlertVariant,
@@ -11,20 +8,21 @@ import {
Switch,
TextInput,
} from "@patternfly/react-core";
-
import type RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
+import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
+import React, { useEffect, useState } from "react";
+import { Controller, useForm, useWatch } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+import { useAlerts } from "../components/alert/Alerts";
import { FormAccess } from "../components/form-access/FormAccess";
import { HelpItem } from "../components/help-enabler/HelpItem";
import { FormPanel } from "../components/scroll-form/FormPanel";
-import { emailRegexPattern } from "../util";
import { useAdminClient } from "../context/auth/AdminClient";
-import { useAlerts } from "../components/alert/Alerts";
import { useRealm } from "../context/realm-context/RealmContext";
-
-import "./RealmSettingsSection.css";
-import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
-import { WhoAmIContext } from "../context/whoami/WhoAmI";
+import { useWhoAmI } from "../context/whoami/WhoAmI";
+import { emailRegexPattern } from "../util";
import { AddUserEmailModal } from "./AddUserEmailModal";
+import "./RealmSettingsSection.css";
type RealmSettingsEmailTabProps = {
realm: RealmRepresentation;
@@ -39,7 +37,7 @@ export const RealmSettingsEmailTab = ({
const adminClient = useAdminClient();
const { realm: realmName } = useRealm();
const { addAlert } = useAlerts();
- const { whoAmI } = useContext(WhoAmIContext);
+ const { whoAmI } = useWhoAmI();
const [realm, setRealm] = useState(initialRealm);
const [userEmailModalOpen, setUserEmailModalOpen] = useState(false);
diff --git a/src/realm-settings/RealmSettingsSection.tsx b/src/realm-settings/RealmSettingsSection.tsx
index 8f51e00d28..9868d1786c 100644
--- a/src/realm-settings/RealmSettingsSection.tsx
+++ b/src/realm-settings/RealmSettingsSection.tsx
@@ -1,7 +1,3 @@
-import React, { useEffect, useState, useContext } from "react";
-import { useHistory } from "react-router-dom";
-import { useTranslation } from "react-i18next";
-import { Controller, FormProvider, useForm } from "react-hook-form";
import {
AlertVariant,
Breadcrumb,
@@ -14,30 +10,33 @@ import {
Tabs,
TabTitleText,
} from "@patternfly/react-core";
-
+import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
import type RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
-import { toUpperCase } from "../util";
+import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
+import React, { useEffect, useState } from "react";
+import { Controller, FormProvider, useForm } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+import { useHistory } from "react-router-dom";
+import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
+import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
+import { ViewHeader } from "../components/view-header/ViewHeader";
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
import { useRealm } from "../context/realm-context/RealmContext";
-import { ViewHeader } from "../components/view-header/ViewHeader";
-import { useAlerts } from "../components/alert/Alerts";
-import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
-import { RealmSettingsLoginTab } from "./LoginTab";
-import { RealmSettingsGeneralTab } from "./GeneralTab";
-import { PartialImportDialog } from "./PartialImport";
-import { RealmSettingsThemesTab } from "./ThemesTab";
-import { RealmSettingsEmailTab } from "./EmailTab";
-import { KeysListTab } from "./KeysListTab";
-import { EventsTab } from "./event-config/EventsTab";
-import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
-import { KeysProvidersTab } from "./KeysProvidersTab";
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
//import { LocalizationTab } from "./LocalizationTab";
-import { WhoAmIContext } from "../context/whoami/WhoAmI";
-import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
+import { useWhoAmI } from "../context/whoami/WhoAmI";
+import { toUpperCase } from "../util";
+import { RealmSettingsEmailTab } from "./EmailTab";
+import { EventsTab } from "./event-config/EventsTab";
+import { RealmSettingsGeneralTab } from "./GeneralTab";
+import { KeysListTab } from "./KeysListTab";
+import { KeysProvidersTab } from "./KeysProvidersTab";
+import { RealmSettingsLoginTab } from "./LoginTab";
+import { PartialImportDialog } from "./PartialImport";
import { SecurityDefences } from "./security-defences/SecurityDefences";
import { RealmSettingsSessionsTab } from "./SessionsTab";
+import { RealmSettingsThemesTab } from "./ThemesTab";
type RealmSettingsHeaderProps = {
onChange: (value: boolean) => void;
@@ -124,9 +123,7 @@ const RealmSettingsHeader = ({
>
{t("partialImport")}
,
- {}}>
- {t("partialExport")}
- ,
+ {t("partialExport")},
,
{t("common:delete")}
@@ -159,7 +156,7 @@ export const RealmSettingsSection = () => {
const [realmComponents, setRealmComponents] =
useState();
const [currentUser, setCurrentUser] = useState();
- const { whoAmI } = useContext(WhoAmIContext);
+ const { whoAmI } = useWhoAmI();
const kpComponentTypes =
useServerInfo().componentTypes!["org.keycloak.keys.KeyProvider"];
diff --git a/src/realm/add/NewRealmForm.tsx b/src/realm/add/NewRealmForm.tsx
index 3cb3bf7d95..8300e56656 100644
--- a/src/realm/add/NewRealmForm.tsx
+++ b/src/realm/add/NewRealmForm.tsx
@@ -1,30 +1,29 @@
-import React, { useContext } from "react";
-import { useHistory } from "react-router-dom";
-import { useTranslation } from "react-i18next";
import {
- PageSection,
- FormGroup,
- TextInput,
- Switch,
ActionGroup,
- Button,
AlertVariant,
+ Button,
+ FormGroup,
+ PageSection,
+ Switch,
+ TextInput,
} from "@patternfly/react-core";
-
-import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
-import { useAlerts } from "../../components/alert/Alerts";
-import { useForm, Controller } from "react-hook-form";
-import { ViewHeader } from "../../components/view-header/ViewHeader";
import type RealmRepresentation from "keycloak-admin/lib/defs/realmRepresentation";
-import { useAdminClient } from "../../context/auth/AdminClient";
-import { WhoAmIContext } from "../../context/whoami/WhoAmI";
+import React from "react";
+import { Controller, useForm } from "react-hook-form";
+import { useTranslation } from "react-i18next";
+import { useHistory } from "react-router-dom";
+import { useAlerts } from "../../components/alert/Alerts";
import { FormAccess } from "../../components/form-access/FormAccess";
+import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
+import { ViewHeader } from "../../components/view-header/ViewHeader";
+import { useAdminClient } from "../../context/auth/AdminClient";
import { useRealm } from "../../context/realm-context/RealmContext";
+import { useWhoAmI } from "../../context/whoami/WhoAmI";
export const NewRealmForm = () => {
const { t } = useTranslation("realm");
const history = useHistory();
- const { refresh } = useContext(WhoAmIContext);
+ const { refresh } = useWhoAmI();
const { refresh: realmRefresh } = useRealm();
const adminClient = useAdminClient();
const { addAlert } = useAlerts();
diff --git a/src/user-federation/UserFederationSection.tsx b/src/user-federation/UserFederationSection.tsx
index c746a837ad..fb6ec80e5b 100644
--- a/src/user-federation/UserFederationSection.tsx
+++ b/src/user-federation/UserFederationSection.tsx
@@ -1,5 +1,3 @@
-import React, { useContext, useState } from "react";
-import { useHistory, useRouteMatch } from "react-router-dom";
import {
AlertVariant,
ButtonVariant,
@@ -15,17 +13,17 @@ import {
TextContent,
TextVariants,
} from "@patternfly/react-core";
-
-import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
-import { KeycloakCard } from "../components/keycloak-card/KeycloakCard";
-import { useAlerts } from "../components/alert/Alerts";
-import { ViewHeader } from "../components/view-header/ViewHeader";
import { DatabaseIcon } from "@patternfly/react-icons";
+import type ComponentRepresentation from "keycloak-admin/lib/defs/componentRepresentation";
+import React, { useState } from "react";
import { useTranslation } from "react-i18next";
-import { RealmContext } from "../context/realm-context/RealmContext";
-import { useAdminClient, useFetch } from "../context/auth/AdminClient";
+import { useHistory, useRouteMatch } from "react-router-dom";
+import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
-
+import { KeycloakCard } from "../components/keycloak-card/KeycloakCard";
+import { ViewHeader } from "../components/view-header/ViewHeader";
+import { useAdminClient, useFetch } from "../context/auth/AdminClient";
+import { useRealm } from "../context/realm-context/RealmContext";
import "./user-federation.css";
export const UserFederationSection = () => {
@@ -33,7 +31,7 @@ export const UserFederationSection = () => {
useState();
const { addAlert } = useAlerts();
const { t } = useTranslation("user-federation");
- const { realm } = useContext(RealmContext);
+ const { realm } = useRealm();
const adminClient = useAdminClient();
const [key, setKey] = useState(0);
const refresh = () => setKey(new Date().getTime());
diff --git a/src/user/UserGroups.tsx b/src/user/UserGroups.tsx
index 75152b664d..6748beaea6 100644
--- a/src/user/UserGroups.tsx
+++ b/src/user/UserGroups.tsx
@@ -1,6 +1,3 @@
-import React, { useContext, useEffect, useState } from "react";
-import { useParams } from "react-router-dom";
-import { useTranslation } from "react-i18next";
import {
AlertVariant,
Button,
@@ -8,20 +5,23 @@ import {
Checkbox,
Popover,
} from "@patternfly/react-core";
-import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
-import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
+import { QuestionCircleIcon } from "@patternfly/react-icons";
+import { cellWidth } from "@patternfly/react-table";
+import type GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
+import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
+import _ from "lodash";
+import React, { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { useParams } from "react-router-dom";
import { useAlerts } from "../components/alert/Alerts";
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
-import { emptyFormatter } from "../util";
-import { useFetch, useAdminClient } from "../context/auth/AdminClient";
-import type GroupRepresentation from "keycloak-admin/lib/defs/groupRepresentation";
-import { cellWidth } from "@patternfly/react-table";
-import _ from "lodash";
-import type UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
-import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
-import { HelpContext } from "../components/help-enabler/HelpHeader";
-import { QuestionCircleIcon } from "@patternfly/react-icons";
import { GroupPath } from "../components/group/GroupPath";
+import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
+import { useHelp } from "../components/help-enabler/HelpHeader";
+import { ListEmptyState } from "../components/list-empty-state/ListEmptyState";
+import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
+import { useAdminClient, useFetch } from "../context/auth/AdminClient";
+import { emptyFormatter } from "../util";
export type UserFormProps = {
username?: string;
@@ -52,7 +52,7 @@ export const UserGroups = () => {
>([]);
const [open, setOpen] = useState(false);
- const { enabled } = useContext(HelpContext);
+ const { enabled } = useHelp();
const adminClient = useAdminClient();
const { id } = useParams<{ id: string }>();
@@ -295,7 +295,6 @@ export const UserGroups = () => {
ariaLabelKey="roles:roleList"
searchPlaceholderKey="groups:searchGroup"
canSelectAll
- onSelect={() => {}}
toolbarItem={
<>