Use react-form-hook
v7 for user IDP modal (#3771)
This commit is contained in:
parent
27d8b35d70
commit
d0b224cae7
4 changed files with 89 additions and 116 deletions
|
@ -11,7 +11,6 @@ export default class IdentityProviderLinksTab {
|
|||
private linkAccountModalIdentityProviderInput = "idpNameInput";
|
||||
private linkAccountModalUserIdInput = "userIdInput";
|
||||
private linkAccountModalUsernameInput = "usernameInput";
|
||||
private linkAccountModalLinkBtn = "Link";
|
||||
|
||||
public clickLinkAccount(idpName: string) {
|
||||
cy.get(this.availableProvidersSection + " tr")
|
||||
|
@ -47,7 +46,7 @@ export default class IdentityProviderLinksTab {
|
|||
}
|
||||
|
||||
public clickLinkAccountModalLinkBtn() {
|
||||
cy.findByTestId(this.linkAccountModalLinkBtn).click();
|
||||
modalUtils.confirmModal();
|
||||
cy.intercept("/admin/realms/master").as("load");
|
||||
cy.wait(["@load"]);
|
||||
return this;
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type FederatedIdentityRepresentation from "@keycloak/keycloak-admin-client/lib/defs/federatedIdentityRepresentation";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
|
@ -8,28 +9,26 @@ import {
|
|||
ModalVariant,
|
||||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { capitalize } from "lodash-es";
|
||||
import { useParams } from "react-router-dom";
|
||||
import type FederatedIdentityRepresentation from "@keycloak/keycloak-admin-client/lib/defs/federatedIdentityRepresentation";
|
||||
import type { UserParams } from "./routes/User";
|
||||
import { useForm } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
|
||||
type UserIdpModalProps = {
|
||||
federatedId?: string;
|
||||
handleModalToggle: () => void;
|
||||
refresh: (group?: GroupRepresentation) => void;
|
||||
userId: string;
|
||||
federatedId: string;
|
||||
onClose: () => void;
|
||||
onRefresh: () => void;
|
||||
};
|
||||
|
||||
export const UserIdpModal = ({
|
||||
userId,
|
||||
federatedId,
|
||||
handleModalToggle,
|
||||
refresh,
|
||||
onClose,
|
||||
onRefresh,
|
||||
}: UserIdpModalProps) => {
|
||||
const { t } = useTranslation("users");
|
||||
const { adminClient } = useAdminClient();
|
||||
|
@ -38,22 +37,22 @@ export const UserIdpModal = ({
|
|||
register,
|
||||
handleSubmit,
|
||||
formState: { isValid, errors },
|
||||
} = useForm({
|
||||
} = useForm<FederatedIdentityRepresentation>({
|
||||
mode: "onChange",
|
||||
});
|
||||
|
||||
const { id } = useParams<UserParams>();
|
||||
|
||||
const submitForm = async (fedIdentity: FederatedIdentityRepresentation) => {
|
||||
const onSubmit = async (
|
||||
federatedIdentity: FederatedIdentityRepresentation
|
||||
) => {
|
||||
try {
|
||||
await adminClient.users.addToFederatedIdentity({
|
||||
id: id!,
|
||||
federatedIdentityId: federatedId!,
|
||||
federatedIdentity: fedIdentity,
|
||||
id: userId,
|
||||
federatedIdentityId: federatedId,
|
||||
federatedIdentity,
|
||||
});
|
||||
addAlert(t("users:idpLinkSuccess"), AlertVariant.success);
|
||||
handleModalToggle();
|
||||
refresh();
|
||||
onClose();
|
||||
onRefresh();
|
||||
} catch (error) {
|
||||
addError("users:couldNotLinkIdP", error);
|
||||
}
|
||||
|
@ -65,12 +64,11 @@ export const UserIdpModal = ({
|
|||
title={t("users:linkAccountTitle", {
|
||||
provider: capitalize(federatedId),
|
||||
})}
|
||||
isOpen={true}
|
||||
onClose={handleModalToggle}
|
||||
onClose={onClose}
|
||||
actions={[
|
||||
<Button
|
||||
data-testid={t("link")}
|
||||
key="confirm"
|
||||
data-testid="confirm"
|
||||
variant="primary"
|
||||
type="submit"
|
||||
form="group-form"
|
||||
|
@ -79,51 +77,31 @@ export const UserIdpModal = ({
|
|||
{t("link")}
|
||||
</Button>,
|
||||
<Button
|
||||
id="modal-cancel"
|
||||
data-testid="cancel"
|
||||
key="cancel"
|
||||
data-testid="cancel"
|
||||
variant={ButtonVariant.link}
|
||||
onClick={() => {
|
||||
handleModalToggle();
|
||||
}}
|
||||
onClick={onClose}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>,
|
||||
]}
|
||||
isOpen
|
||||
>
|
||||
<Form id="group-form" onSubmit={handleSubmit(submitForm)}>
|
||||
<Form id="group-form" onSubmit={handleSubmit(onSubmit)}>
|
||||
<FormGroup
|
||||
name="idp-name-group"
|
||||
label={t("users:identityProvider")}
|
||||
fieldId="idp-name"
|
||||
helperTextInvalid={t("common:required")}
|
||||
validated={
|
||||
errors.identityProvider
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
fieldId="identityProvider"
|
||||
>
|
||||
<KeycloakTextInput
|
||||
id="identityProvider"
|
||||
data-testid="idpNameInput"
|
||||
aria-label="Identity provider name input"
|
||||
ref={register({ required: true })}
|
||||
autoFocus
|
||||
isReadOnly
|
||||
type="text"
|
||||
id="link-idp-name"
|
||||
name="identityProvider"
|
||||
value={capitalize(federatedId)}
|
||||
validated={
|
||||
errors.identityProvider
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
isReadOnly
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
name="user-id-group"
|
||||
label={t("users:userID")}
|
||||
fieldId="user-id"
|
||||
fieldId="userID"
|
||||
helperText={t("users-help:userIdHelperText")}
|
||||
helperTextInvalid={t("common:required")}
|
||||
validated={
|
||||
|
@ -132,40 +110,34 @@ export const UserIdpModal = ({
|
|||
isRequired
|
||||
>
|
||||
<KeycloakTextInput
|
||||
id="userID"
|
||||
data-testid="userIdInput"
|
||||
aria-label="user ID input"
|
||||
ref={register({ required: true })}
|
||||
autoFocus
|
||||
type="text"
|
||||
id="link-idp-user-id"
|
||||
name="userId"
|
||||
validated={
|
||||
errors.userId ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
autoFocus
|
||||
{...register("userId", { required: true })}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
name="username-group"
|
||||
label={t("users:username")}
|
||||
fieldId="username"
|
||||
helperText={t("users-help:usernameHelperText")}
|
||||
helperTextInvalid={t("common:required")}
|
||||
validated={
|
||||
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||
errors.userName ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
isRequired
|
||||
>
|
||||
<KeycloakTextInput
|
||||
id="username"
|
||||
data-testid="usernameInput"
|
||||
aria-label="username input"
|
||||
ref={register({ required: true })}
|
||||
autoFocus
|
||||
type="text"
|
||||
id="link-idp-username"
|
||||
name="userName"
|
||||
validated={
|
||||
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||
errors.userName
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
{...register("userName", { required: true })}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type FederatedIdentityRepresentation from "@keycloak/keycloak-admin-client/lib/defs/federatedIdentityRepresentation";
|
||||
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
|
@ -9,40 +9,41 @@ import {
|
|||
Text,
|
||||
TextContent,
|
||||
} from "@patternfly/react-core";
|
||||
import { cellWidth } from "@patternfly/react-table";
|
||||
import { capitalize } from "lodash-es";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom-v5-compat";
|
||||
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { FormPanel } from "../components/scroll-form/FormPanel";
|
||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||
import { cellWidth } from "@patternfly/react-table";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { Link } from "react-router-dom-v5-compat";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { emptyFormatter, upperCaseFormatter } from "../util";
|
||||
import type IdentityProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/identityProviderRepresentation";
|
||||
import type FederatedIdentityRepresentation from "@keycloak/keycloak-admin-client/lib/defs/federatedIdentityRepresentation";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import { capitalize } from "lodash-es";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { UserIdpModal } from "./UserIdPModal";
|
||||
import { toIdentityProvider } from "../identity-providers/routes/IdentityProvider";
|
||||
import { emptyFormatter, upperCaseFormatter } from "../util";
|
||||
import { UserIdpModal } from "./UserIdPModal";
|
||||
|
||||
export const UserIdentityProviderLinks = () => {
|
||||
type UserIdentityProviderLinksProps = {
|
||||
userId: string;
|
||||
};
|
||||
|
||||
export const UserIdentityProviderLinks = ({
|
||||
userId,
|
||||
}: UserIdentityProviderLinksProps) => {
|
||||
const [key, setKey] = useState(0);
|
||||
const [federatedId, setFederatedId] = useState("");
|
||||
const [isLinkIdPModalOpen, setIsLinkIdPModalOpen] = useState(false);
|
||||
|
||||
const { adminClient } = useAdminClient();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { realm } = useRealm();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const { t } = useTranslation("users");
|
||||
|
||||
const refresh = () => setKey(new Date().getTime());
|
||||
|
||||
const handleModalToggle = () => {
|
||||
setIsLinkIdPModalOpen(!isLinkIdPModalOpen);
|
||||
};
|
||||
|
||||
type WithProviderId = FederatedIdentityRepresentation & {
|
||||
providerId: string;
|
||||
};
|
||||
|
@ -53,7 +54,7 @@ export const UserIdentityProviderLinks = () => {
|
|||
const allProviders = await adminClient.identityProviders.find();
|
||||
|
||||
const allFedIds = (await adminClient.users.listFederatedIdentities({
|
||||
id,
|
||||
id: userId,
|
||||
})) as WithProviderId[];
|
||||
for (const element of allFedIds) {
|
||||
element.providerId = allProviders.find(
|
||||
|
@ -94,7 +95,7 @@ export const UserIdentityProviderLinks = () => {
|
|||
onConfirm: async () => {
|
||||
try {
|
||||
await adminClient.users.delFromFederatedIdentity({
|
||||
id,
|
||||
id: userId,
|
||||
federatedIdentityId: federatedId,
|
||||
});
|
||||
addAlert(t("users:idpUnlinkSuccess"), AlertVariant.success);
|
||||
|
@ -180,9 +181,10 @@ export const UserIdentityProviderLinks = () => {
|
|||
<>
|
||||
{isLinkIdPModalOpen && (
|
||||
<UserIdpModal
|
||||
userId={userId}
|
||||
federatedId={federatedId}
|
||||
handleModalToggle={handleModalToggle}
|
||||
refresh={refresh}
|
||||
onClose={() => setIsLinkIdPModalOpen(false)}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
)}
|
||||
<UnlinkConfirm />
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { useState } from "react";
|
||||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import {
|
||||
AlertVariant,
|
||||
ButtonVariant,
|
||||
|
@ -7,31 +8,30 @@ import {
|
|||
Tab,
|
||||
TabTitleText,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useState } from "react";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { BruteForced, UserForm } from "./UserForm";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useNavigate } from "react-router-dom-v5-compat";
|
||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||
import { UserGroups } from "./UserGroups";
|
||||
import { UserConsents } from "./UserConsents";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { UserIdentityProviderLinks } from "./UserIdentityProviderLinks";
|
||||
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { toUser } from "./routes/User";
|
||||
import { toUsers } from "./routes/Users";
|
||||
import { UserRoleMapping } from "./UserRoleMapping";
|
||||
import { UserAttributes } from "./UserAttributes";
|
||||
import { UserCredentials } from "./UserCredentials";
|
||||
import { UserSessions } from "./UserSessions";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
import { useAdminClient, useFetch } from "../context/auth/AdminClient";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { toUser, UserParams } from "./routes/User";
|
||||
import { toUsers } from "./routes/Users";
|
||||
import { UserAttributes } from "./UserAttributes";
|
||||
import { UserConsents } from "./UserConsents";
|
||||
import { UserCredentials } from "./UserCredentials";
|
||||
import { BruteForced, UserForm } from "./UserForm";
|
||||
import { UserGroups } from "./UserGroups";
|
||||
import { UserIdentityProviderLinks } from "./UserIdentityProviderLinks";
|
||||
import { UserRoleMapping } from "./UserRoleMapping";
|
||||
import { UserSessions } from "./UserSessions";
|
||||
|
||||
const UsersTabs = () => {
|
||||
const { t } = useTranslation("users");
|
||||
|
@ -42,7 +42,7 @@ const UsersTabs = () => {
|
|||
|
||||
const { adminClient } = useAdminClient();
|
||||
const userForm = useForm<UserRepresentation>({ mode: "onChange" });
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const { id } = useParams<UserParams>();
|
||||
const [user, setUser] = useState<UserRepresentation>();
|
||||
const [bruteForced, setBruteForced] = useState<BruteForced>();
|
||||
const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]);
|
||||
|
@ -247,7 +247,7 @@ const UsersTabs = () => {
|
|||
<TabTitleText>{t("identityProviderLinks")}</TabTitleText>
|
||||
}
|
||||
>
|
||||
<UserIdentityProviderLinks />
|
||||
<UserIdentityProviderLinks userId={id} />
|
||||
</Tab>
|
||||
)}
|
||||
<Tab
|
||||
|
|
Loading…
Reference in a new issue