From 41a0923bade63758c648c8e9c6d0f44d45c323ec Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Wed, 21 Jul 2021 11:30:18 +0200 Subject: [PATCH] Introduce 'useRequiredContext' utility function (#882) --- package-lock.json | 94 +++++++++++++++++++ package.json | 1 + src/PageHeader.tsx | 14 +-- src/client-scopes/add/RoleMappingForm.tsx | 34 ++++--- src/clients/scopes/EvaluateScopes.tsx | 24 +++-- src/components/alert/Alerts.tsx | 11 +-- .../download-dialog/DownloadDialog.tsx | 13 ++- src/components/help-enabler/HelpHeader.tsx | 17 ++-- src/components/help-enabler/HelpItem.tsx | 6 +- .../realm-selector/RealmSelector.tsx | 25 +++-- src/components/view-header/ViewHeader.tsx | 20 ++-- src/context/access/Access.tsx | 21 ++--- src/context/auth/AdminClient.tsx | 8 +- src/context/realm-context/RealmContext.tsx | 19 ++-- .../server-info/ServerInfoProvider.tsx | 14 +-- src/context/whoami/WhoAmI.tsx | 15 +-- src/events/AdminEvents.tsx | 17 ++-- src/events/EventsSection.tsx | 26 +++-- src/groups/SubGroupsContext.tsx | 15 +-- src/realm-roles/UsersInRoleTab.tsx | 14 +-- src/realm-settings/EmailTab.tsx | 20 ++-- src/realm-settings/RealmSettingsSection.tsx | 45 +++++---- src/realm/add/NewRealmForm.tsx | 31 +++--- src/user-federation/UserFederationSection.tsx | 20 ++-- src/user/UserGroups.tsx | 31 +++--- src/user/UsersSection.tsx | 24 +++-- src/utils/useRequiredContext.test.ts | 47 ++++++++++ src/utils/useRequiredContext.ts | 25 +++++ 28 files changed, 393 insertions(+), 258 deletions(-) create mode 100644 src/utils/useRequiredContext.test.ts create mode 100644 src/utils/useRequiredContext.ts 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={ <>