migrated user forms to ui-shared (#27593)
* migrated user forms to ui-shared Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * review comments Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> * fixed test Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com> --------- Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
39299eeb38
commit
b0b967d8d4
10 changed files with 284 additions and 451 deletions
|
@ -6,16 +6,16 @@ export default class CredentialsPage {
|
||||||
readonly #setPasswordBtn = "confirm";
|
readonly #setPasswordBtn = "confirm";
|
||||||
readonly #credentialResetModal = "credential-reset-modal";
|
readonly #credentialResetModal = "credential-reset-modal";
|
||||||
readonly #resetModalActionsToggleBtn =
|
readonly #resetModalActionsToggleBtn =
|
||||||
"[data-testid=credential-reset-modal] #actions-actions";
|
"[data-testid=credential-reset-modal] #actions";
|
||||||
|
|
||||||
readonly #passwordField = "passwordField";
|
readonly #passwordField = "passwordField";
|
||||||
readonly #passwordConfirmationField = "passwordConfirmationField";
|
readonly #passwordConfirmationField = "passwordConfirmationField";
|
||||||
readonly #resetActions = [
|
readonly #resetActions = [
|
||||||
"VERIFY_EMAIL-option",
|
"Verify Email",
|
||||||
"UPDATE_PROFILE-option",
|
"Update Profile",
|
||||||
"CONFIGURE_TOTP-option",
|
"Configure OTP",
|
||||||
"UPDATE_PASSWORD-option",
|
"Update Password",
|
||||||
"TERMS_AND_CONDITIONS-option",
|
"Terms and Conditions",
|
||||||
];
|
];
|
||||||
readonly #confirmationButton = "confirm";
|
readonly #confirmationButton = "confirm";
|
||||||
readonly #editLabelBtn = "editUserLabelBtn";
|
readonly #editLabelBtn = "editUserLabelBtn";
|
||||||
|
@ -57,7 +57,9 @@ export default class CredentialsPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
clickResetModalAction(index: number) {
|
clickResetModalAction(index: number) {
|
||||||
cy.findByTestId(this.#resetActions[index]).click();
|
cy.get("[data-testid=credential-reset-modal] .pf-c-select__menu")
|
||||||
|
.contains(this.#resetActions[index])
|
||||||
|
.click();
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,8 +9,8 @@ export default class IdentityProviderLinksTab {
|
||||||
#availableProvidersSection = ".kc-available-idps";
|
#availableProvidersSection = ".kc-available-idps";
|
||||||
#linkAccountBtn = ".pf-c-button.pf-m-link";
|
#linkAccountBtn = ".pf-c-button.pf-m-link";
|
||||||
#linkAccountModalIdentityProviderInput = "idpNameInput";
|
#linkAccountModalIdentityProviderInput = "idpNameInput";
|
||||||
#linkAccountModalUserIdInput = "userIdInput";
|
#linkAccountModalUserIdInput = "userId";
|
||||||
#linkAccountModalUsernameInput = "usernameInput";
|
#linkAccountModalUsernameInput = "userName";
|
||||||
|
|
||||||
public clickLinkAccount(idpName: string) {
|
public clickLinkAccount(idpName: string) {
|
||||||
cy.get(this.#availableProvidersSection + " tr")
|
cy.get(this.#availableProvidersSection + " tr")
|
||||||
|
|
|
@ -2853,6 +2853,7 @@ dropNonexistingGroupsDuringSync=Drop non-existing groups during sync
|
||||||
clientAssertionSigningAlgHelp=Signature algorithm to create JWT assertion as client authentication. In the case of JWT signed with private key or JWT signed with client secret, it is required. If no algorithm is specified, the following algorithm is adapted. RS256 is adapted in the case of JWT signed with private key. HS256 is adapted in the case of JWT signed with client secret.
|
clientAssertionSigningAlgHelp=Signature algorithm to create JWT assertion as client authentication. In the case of JWT signed with private key or JWT signed with client secret, it is required. If no algorithm is specified, the following algorithm is adapted. RS256 is adapted in the case of JWT signed with private key. HS256 is adapted in the case of JWT signed with client secret.
|
||||||
jwtX509HeadersEnabledHelp=If enabled, the x5t (X.509 Certificate SHA-1 Thumbprint) header will be added to the JWT to reference the certificate used to sign it. Otherwise, the kid (Key ID) header will be used instead.
|
jwtX509HeadersEnabledHelp=If enabled, the x5t (X.509 Certificate SHA-1 Thumbprint) header will be added to the JWT to reference the certificate used to sign it. Otherwise, the kid (Key ID) header will be used instead.
|
||||||
addProvider_other=Add {{provider}} providers
|
addProvider_other=Add {{provider}} providers
|
||||||
|
resetAction=Reset action
|
||||||
cibaExpiresIn=Expires In
|
cibaExpiresIn=Expires In
|
||||||
dynamicScopeFormatHelp=This is the regular expression that the system will use to extract the scope name and variable.
|
dynamicScopeFormatHelp=This is the regular expression that the system will use to extract the scope name and variable.
|
||||||
updateTranslationError=Error updating translation.
|
updateTranslationError=Error updating translation.
|
||||||
|
|
|
@ -13,13 +13,18 @@ import {
|
||||||
Switch,
|
Switch,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { TFunction } from "i18next";
|
import { TFunction } from "i18next";
|
||||||
import { useState, useEffect } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, UseFormReturn } from "react-hook-form";
|
import { Controller, FormProvider, UseFormReturn } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { HelpItem, UserProfileFields } from "ui-shared";
|
import {
|
||||||
|
HelpItem,
|
||||||
|
SwitchControl,
|
||||||
|
TextControl,
|
||||||
|
UserProfileFields,
|
||||||
|
} from "ui-shared";
|
||||||
import { adminClient } from "../admin-client";
|
import { adminClient } from "../admin-client";
|
||||||
|
import { DefaultSwitchControl } from "../components/SwitchControl";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { FormAccess } from "../components/form/FormAccess";
|
import { FormAccess } from "../components/form/FormAccess";
|
||||||
import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
|
import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
|
||||||
|
@ -68,7 +73,6 @@ export const UserForm = ({
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
register,
|
|
||||||
setValue,
|
setValue,
|
||||||
watch,
|
watch,
|
||||||
control,
|
control,
|
||||||
|
@ -133,6 +137,7 @@ export const UserForm = ({
|
||||||
fineGrainedAccess={user?.access?.manage}
|
fineGrainedAccess={user?.access?.manage}
|
||||||
className="pf-u-mt-lg"
|
className="pf-u-mt-lg"
|
||||||
>
|
>
|
||||||
|
<FormProvider {...form}>
|
||||||
{open && (
|
{open && (
|
||||||
<GroupPickerDialog
|
<GroupPickerDialog
|
||||||
type="selectMany"
|
type="selectMany"
|
||||||
|
@ -156,24 +161,23 @@ export const UserForm = ({
|
||||||
id={user.id}
|
id={user.id}
|
||||||
aria-label={t("userID")}
|
aria-label={t("userID")}
|
||||||
value={user.id}
|
value={user.id}
|
||||||
type="text"
|
readOnly
|
||||||
isReadOnly
|
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup label={t("createdAt")} fieldId="kc-created-at" isRequired>
|
<FormGroup
|
||||||
|
label={t("createdAt")}
|
||||||
|
fieldId="kc-created-at"
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
<KeycloakTextInput
|
<KeycloakTextInput
|
||||||
value={formatDate(new Date(user.createdTimestamp!))}
|
value={formatDate(new Date(user.createdTimestamp!))}
|
||||||
type="text"
|
|
||||||
id="kc-created-at"
|
id="kc-created-at"
|
||||||
aria-label={t("createdAt")}
|
readOnly
|
||||||
name="createdTimestamp"
|
|
||||||
isReadOnly
|
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<RequiredActionMultiSelect
|
<RequiredActionMultiSelect
|
||||||
control={control}
|
|
||||||
name="requiredActions"
|
name="requiredActions"
|
||||||
label="requiredUserActions"
|
label="requiredUserActions"
|
||||||
help="requiredUserActionsHelp"
|
help="requiredUserActionsHelp"
|
||||||
|
@ -193,33 +197,11 @@ export const UserForm = ({
|
||||||
)}
|
)}
|
||||||
{userProfileMetadata ? (
|
{userProfileMetadata ? (
|
||||||
<>
|
<>
|
||||||
<FormGroup
|
<DefaultSwitchControl
|
||||||
label={t("emailVerified")}
|
|
||||||
fieldId="kc-email-verified"
|
|
||||||
helperTextInvalid={t("required")}
|
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText={t("emailVerifiedHelp")}
|
|
||||||
fieldLabelId="emailVerified"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
name="emailVerified"
|
name="emailVerified"
|
||||||
defaultValue={false}
|
label={t("emailVerified")}
|
||||||
control={control}
|
labelIcon={t("emailVerifiedHelp")}
|
||||||
render={({ field }) => (
|
|
||||||
<Switch
|
|
||||||
data-testid="email-verified-switch"
|
|
||||||
id="kc-user-email-verified"
|
|
||||||
onChange={(value) => field.onChange(value)}
|
|
||||||
isChecked={field.value}
|
|
||||||
label={t("yes")}
|
|
||||||
labelOff={t("no")}
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<UserProfileFields
|
<UserProfileFields
|
||||||
form={form}
|
form={form}
|
||||||
userProfileMetadata={userProfileMetadata}
|
userProfileMetadata={userProfileMetadata}
|
||||||
|
@ -234,90 +216,39 @@ export const UserForm = ({
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{!realm.registrationEmailAsUsername && (
|
{!realm.registrationEmailAsUsername && (
|
||||||
<FormGroup
|
<TextControl
|
||||||
|
name="username"
|
||||||
label={t("username")}
|
label={t("username")}
|
||||||
fieldId="kc-username"
|
readOnly={
|
||||||
isRequired
|
|
||||||
validated={errors.username ? "error" : "default"}
|
|
||||||
helperTextInvalid={t("required")}
|
|
||||||
>
|
|
||||||
<KeycloakTextInput
|
|
||||||
id="kc-username"
|
|
||||||
isReadOnly={
|
|
||||||
!!user?.id &&
|
!!user?.id &&
|
||||||
!realm.editUsernameAllowed &&
|
!realm.editUsernameAllowed &&
|
||||||
realm.editUsernameAllowed !== undefined
|
realm.editUsernameAllowed !== undefined
|
||||||
}
|
}
|
||||||
{...register("username")}
|
rules={{
|
||||||
|
required: t("required"),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
|
||||||
)}
|
)}
|
||||||
<FormGroup
|
<TextControl
|
||||||
|
name="email"
|
||||||
label={t("email")}
|
label={t("email")}
|
||||||
fieldId="kc-email"
|
|
||||||
validated={errors.email ? "error" : "default"}
|
|
||||||
helperTextInvalid={t("emailInvalid")}
|
|
||||||
>
|
|
||||||
<KeycloakTextInput
|
|
||||||
type="email"
|
type="email"
|
||||||
id="kc-email"
|
rules={{
|
||||||
data-testid="email-input"
|
pattern: {
|
||||||
{...register("email", {
|
value: emailRegexPattern,
|
||||||
pattern: emailRegexPattern,
|
message: t("emailInvalid"),
|
||||||
})}
|
},
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
<SwitchControl
|
||||||
<FormGroup
|
|
||||||
label={t("emailVerified")}
|
|
||||||
fieldId="kc-email-verified"
|
|
||||||
helperTextInvalid={t("required")}
|
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText={t("emailVerifiedHelp")}
|
|
||||||
fieldLabelId="emailVerified"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
name="emailVerified"
|
name="emailVerified"
|
||||||
defaultValue={false}
|
label={t("emailVerified")}
|
||||||
control={control}
|
labelIcon={t("emailVerifiedHelp")}
|
||||||
render={({ field }) => (
|
labelOn={t("yes")}
|
||||||
<Switch
|
|
||||||
data-testid="email-verified-switch"
|
|
||||||
id="kc-user-email-verified"
|
|
||||||
onChange={(value) => field.onChange(value)}
|
|
||||||
isChecked={field.value}
|
|
||||||
label={t("yes")}
|
|
||||||
labelOff={t("no")}
|
labelOff={t("no")}
|
||||||
/>
|
/>
|
||||||
)}
|
<TextControl name="firstName" label={t("firstName")} />
|
||||||
/>
|
<TextControl name="lastName" label={t("lastName")} />
|
||||||
</FormGroup>
|
|
||||||
<FormGroup
|
|
||||||
label={t("firstName")}
|
|
||||||
fieldId="kc-firstName"
|
|
||||||
validated={errors.firstName ? "error" : "default"}
|
|
||||||
helperTextInvalid={t("required")}
|
|
||||||
>
|
|
||||||
<KeycloakTextInput
|
|
||||||
data-testid="firstName-input"
|
|
||||||
id="kc-firstName"
|
|
||||||
{...register("firstName")}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<FormGroup
|
|
||||||
label={t("lastName")}
|
|
||||||
fieldId="kc-lastName"
|
|
||||||
validated={errors.lastName ? "error" : "default"}
|
|
||||||
>
|
|
||||||
<KeycloakTextInput
|
|
||||||
data-testid="lastName-input"
|
|
||||||
id="kc-lastName"
|
|
||||||
aria-label={t("lastName")}
|
|
||||||
{...register("lastName")}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{isBruteForceProtected && (
|
{isBruteForceProtected && (
|
||||||
|
@ -351,7 +282,9 @@ export const UserForm = ({
|
||||||
fieldId="kc-groups"
|
fieldId="kc-groups"
|
||||||
validated={errors.requiredActions ? "error" : "default"}
|
validated={errors.requiredActions ? "error" : "default"}
|
||||||
helperTextInvalid={t("required")}
|
helperTextInvalid={t("required")}
|
||||||
labelIcon={<HelpItem helpText={t("groups")} fieldLabelId="groups" />}
|
labelIcon={
|
||||||
|
<HelpItem helpText={t("groups")} fieldLabelId="groups" />
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
name="groups"
|
name="groups"
|
||||||
|
@ -411,6 +344,7 @@ export const UserForm = ({
|
||||||
{user?.id ? t("revert") : t("cancel")}
|
{user?.id ? t("revert") : t("cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
</ActionGroup>
|
</ActionGroup>
|
||||||
|
</FormProvider>
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,12 +7,11 @@ import {
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Modal,
|
Modal,
|
||||||
ModalVariant,
|
ModalVariant,
|
||||||
ValidatedOptions,
|
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { capitalize } from "lodash-es";
|
import { capitalize } from "lodash-es";
|
||||||
import { useForm } from "react-hook-form";
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { TextControl } from "ui-shared";
|
||||||
import { adminClient } from "../admin-client";
|
import { adminClient } from "../admin-client";
|
||||||
import { useAlerts } from "../components/alert/Alerts";
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
|
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
|
||||||
|
@ -32,13 +31,13 @@ export const UserIdpModal = ({
|
||||||
}: UserIdpModalProps) => {
|
}: UserIdpModalProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { addAlert, addError } = useAlerts();
|
const { addAlert, addError } = useAlerts();
|
||||||
const {
|
const form = useForm<FederatedIdentityRepresentation>({
|
||||||
register,
|
|
||||||
handleSubmit,
|
|
||||||
formState: { isValid, errors },
|
|
||||||
} = useForm<FederatedIdentityRepresentation>({
|
|
||||||
mode: "onChange",
|
mode: "onChange",
|
||||||
});
|
});
|
||||||
|
const {
|
||||||
|
handleSubmit,
|
||||||
|
formState: { isValid },
|
||||||
|
} = form;
|
||||||
|
|
||||||
const onSubmit = async (
|
const onSubmit = async (
|
||||||
federatedIdentity: FederatedIdentityRepresentation,
|
federatedIdentity: FederatedIdentityRepresentation,
|
||||||
|
@ -87,55 +86,33 @@ export const UserIdpModal = ({
|
||||||
isOpen
|
isOpen
|
||||||
>
|
>
|
||||||
<Form id="group-form" onSubmit={handleSubmit(onSubmit)}>
|
<Form id="group-form" onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<FormProvider {...form}>
|
||||||
<FormGroup label={t("identityProvider")} fieldId="identityProvider">
|
<FormGroup label={t("identityProvider")} fieldId="identityProvider">
|
||||||
<KeycloakTextInput
|
<KeycloakTextInput
|
||||||
id="identityProvider"
|
id="identityProvider"
|
||||||
data-testid="idpNameInput"
|
data-testid="idpNameInput"
|
||||||
value={capitalize(federatedId)}
|
value={capitalize(federatedId)}
|
||||||
isReadOnly
|
readOnly
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup
|
<TextControl
|
||||||
|
name="userId"
|
||||||
label={t("userID")}
|
label={t("userID")}
|
||||||
fieldId="userID"
|
|
||||||
helperText={t("userIdHelperText")}
|
helperText={t("userIdHelperText")}
|
||||||
helperTextInvalid={t("required")}
|
|
||||||
validated={
|
|
||||||
errors.userId ? ValidatedOptions.error : ValidatedOptions.default
|
|
||||||
}
|
|
||||||
isRequired
|
|
||||||
>
|
|
||||||
<KeycloakTextInput
|
|
||||||
id="userID"
|
|
||||||
data-testid="userIdInput"
|
|
||||||
validated={
|
|
||||||
errors.userId ? ValidatedOptions.error : ValidatedOptions.default
|
|
||||||
}
|
|
||||||
autoFocus
|
autoFocus
|
||||||
{...register("userId", { required: true })}
|
rules={{
|
||||||
|
required: t("required"),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
<TextControl
|
||||||
<FormGroup
|
name="userName"
|
||||||
label={t("username")}
|
label={t("username")}
|
||||||
fieldId="username"
|
|
||||||
helperText={t("usernameHelperText")}
|
helperText={t("usernameHelperText")}
|
||||||
helperTextInvalid={t("required")}
|
rules={{
|
||||||
validated={
|
required: t("required"),
|
||||||
errors.userName ? ValidatedOptions.error : ValidatedOptions.default
|
}}
|
||||||
}
|
|
||||||
isRequired
|
|
||||||
>
|
|
||||||
<KeycloakTextInput
|
|
||||||
id="username"
|
|
||||||
data-testid="usernameInput"
|
|
||||||
validated={
|
|
||||||
errors.userName
|
|
||||||
? ValidatedOptions.error
|
|
||||||
: ValidatedOptions.default
|
|
||||||
}
|
|
||||||
{...register("userName", { required: true })}
|
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormProvider>
|
||||||
</Form>
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,37 +1,20 @@
|
||||||
import { FormGroup } from "@patternfly/react-core";
|
|
||||||
import { Controller, useFormContext } from "react-hook-form";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { TimeSelectorControl } from "../../components/time-selector/TimeSelectorControl";
|
||||||
import { HelpItem } from "ui-shared";
|
|
||||||
import { TimeSelector } from "../../components/time-selector/TimeSelector";
|
|
||||||
import { credResetFormDefaultValues } from "./ResetCredentialDialog";
|
import { credResetFormDefaultValues } from "./ResetCredentialDialog";
|
||||||
|
|
||||||
export const LifespanField = () => {
|
export const LifespanField = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { control } = useFormContext();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<TimeSelectorControl
|
||||||
fieldId="lifespan"
|
|
||||||
label={t("lifespan")}
|
|
||||||
isStack
|
|
||||||
labelIcon={
|
|
||||||
<HelpItem helpText={t("lifespanHelp")} fieldLabelId="lifespan" />
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
name="lifespan"
|
name="lifespan"
|
||||||
defaultValue={credResetFormDefaultValues.lifespan}
|
label={t("lifespan")}
|
||||||
control={control}
|
labelIcon={t("lifespanHelp")}
|
||||||
render={({ field }) => (
|
|
||||||
<TimeSelector
|
|
||||||
value={field.value}
|
|
||||||
units={["minute", "hour", "day"]}
|
units={["minute", "hour", "day"]}
|
||||||
onChange={field.onChange}
|
|
||||||
menuAppendTo="parent"
|
menuAppendTo="parent"
|
||||||
|
controller={{
|
||||||
|
defaultValue: credResetFormDefaultValues.lifespan,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,20 +1,9 @@
|
||||||
import type RequiredActionProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
|
import type RequiredActionProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
|
||||||
import {
|
import { SelectVariant } from "@patternfly/react-core";
|
||||||
FormGroup,
|
|
||||||
Select,
|
|
||||||
SelectOption,
|
|
||||||
SelectVariant,
|
|
||||||
} from "@patternfly/react-core";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import {
|
import { FieldPathByValue, FieldValues } from "react-hook-form";
|
||||||
Control,
|
|
||||||
Controller,
|
|
||||||
FieldPathByValue,
|
|
||||||
FieldValues,
|
|
||||||
PathValue,
|
|
||||||
} from "react-hook-form";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { HelpItem } from "ui-shared";
|
import { SelectControl } from "ui-shared";
|
||||||
|
|
||||||
import { adminClient } from "../../admin-client";
|
import { adminClient } from "../../admin-client";
|
||||||
import { useFetch } from "../../utils/useFetch";
|
import { useFetch } from "../../utils/useFetch";
|
||||||
|
@ -23,7 +12,6 @@ export type RequiredActionMultiSelectProps<
|
||||||
T extends FieldValues,
|
T extends FieldValues,
|
||||||
P extends FieldPathByValue<T, string[] | undefined>,
|
P extends FieldPathByValue<T, string[] | undefined>,
|
||||||
> = {
|
> = {
|
||||||
control: Control<T>;
|
|
||||||
name: P;
|
name: P;
|
||||||
label: string;
|
label: string;
|
||||||
help: string;
|
help: string;
|
||||||
|
@ -33,13 +21,11 @@ export const RequiredActionMultiSelect = <
|
||||||
T extends FieldValues,
|
T extends FieldValues,
|
||||||
P extends FieldPathByValue<T, string[] | undefined>,
|
P extends FieldPathByValue<T, string[] | undefined>,
|
||||||
>({
|
>({
|
||||||
control,
|
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
help,
|
help,
|
||||||
}: RequiredActionMultiSelectProps<T, P>) => {
|
}: RequiredActionMultiSelectProps<T, P>) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [requiredActions, setRequiredActions] = useState<
|
const [requiredActions, setRequiredActions] = useState<
|
||||||
RequiredActionProviderRepresentation[]
|
RequiredActionProviderRepresentation[]
|
||||||
>([]);
|
>([]);
|
||||||
|
@ -56,54 +42,23 @@ export const RequiredActionMultiSelect = <
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormGroup
|
<SelectControl
|
||||||
label={t(label)}
|
|
||||||
labelIcon={<HelpItem helpText={t(help)} fieldLabelId="resetAction" />}
|
|
||||||
fieldId="actions"
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
name={name}
|
name={name}
|
||||||
defaultValue={[] as PathValue<T, P>}
|
label={t(label)}
|
||||||
control={control}
|
labelIcon={t(help)}
|
||||||
render={({ field }) => (
|
controller={{ defaultValue: [] }}
|
||||||
<Select
|
|
||||||
maxHeight={375}
|
maxHeight={375}
|
||||||
toggleId={`${name}-actions`}
|
|
||||||
variant={SelectVariant.typeaheadMulti}
|
variant={SelectVariant.typeaheadMulti}
|
||||||
chipGroupProps={{
|
chipGroupProps={{
|
||||||
numChips: 3,
|
numChips: 3,
|
||||||
}}
|
}}
|
||||||
placeholderText={t("requiredActionPlaceholder")}
|
placeholderText={t("requiredActionPlaceholder")}
|
||||||
menuAppendTo="parent"
|
menuAppendTo="parent"
|
||||||
onToggle={(open) => setOpen(open)}
|
|
||||||
isOpen={open}
|
|
||||||
selections={field.value as string[]}
|
|
||||||
onSelect={(_, selectedValue) => {
|
|
||||||
const value: string[] = field.value;
|
|
||||||
field.onChange(
|
|
||||||
value.find((item) => item === selectedValue)
|
|
||||||
? value.filter((item) => item !== selectedValue)
|
|
||||||
: [...value, selectedValue],
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
onClear={(event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
field.onChange([]);
|
|
||||||
}}
|
|
||||||
typeAheadAriaLabel={t("resetAction")}
|
typeAheadAriaLabel={t("resetAction")}
|
||||||
>
|
options={requiredActions.map(({ alias, name }) => ({
|
||||||
{requiredActions.map(({ alias, name }) => (
|
key: alias!,
|
||||||
<SelectOption
|
value: name || alias!,
|
||||||
key={alias}
|
}))}
|
||||||
value={alias}
|
|
||||||
data-testid={`${alias}-option`}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</SelectOption>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
)}
|
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -82,13 +82,12 @@ export const ResetCredentialDialog = ({
|
||||||
isHorizontal
|
isHorizontal
|
||||||
data-testid="credential-reset-modal"
|
data-testid="credential-reset-modal"
|
||||||
>
|
>
|
||||||
|
<FormProvider {...form}>
|
||||||
<RequiredActionMultiSelect
|
<RequiredActionMultiSelect
|
||||||
control={control}
|
|
||||||
name="actions"
|
name="actions"
|
||||||
label="resetAction"
|
label="resetAction"
|
||||||
help="resetActions"
|
help="resetActions"
|
||||||
/>
|
/>
|
||||||
<FormProvider {...form}>
|
|
||||||
<LifespanField />
|
<LifespanField />
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
|
@ -1,18 +1,16 @@
|
||||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
|
||||||
import { RequiredActionAlias } from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
|
import { RequiredActionAlias } from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
|
||||||
|
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||||
import {
|
import {
|
||||||
AlertVariant,
|
AlertVariant,
|
||||||
ButtonVariant,
|
ButtonVariant,
|
||||||
Form,
|
Form,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
Switch,
|
|
||||||
ValidatedOptions,
|
ValidatedOptions,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { FormProvider, useForm } from "react-hook-form";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { HelpItem } from "ui-shared";
|
|
||||||
|
|
||||||
import { adminClient } from "../../admin-client";
|
import { adminClient } from "../../admin-client";
|
||||||
|
import { DefaultSwitchControl } from "../../components/SwitchControl";
|
||||||
import { useAlerts } from "../../components/alert/Alerts";
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
import {
|
import {
|
||||||
ConfirmDialogModal,
|
ConfirmDialogModal,
|
||||||
|
@ -49,18 +47,18 @@ export const ResetPasswordDialog = ({
|
||||||
onClose,
|
onClose,
|
||||||
}: ResetPasswordDialogProps) => {
|
}: ResetPasswordDialogProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const form = useForm<CredentialsForm>({
|
||||||
|
defaultValues: credFormDefaultValues,
|
||||||
|
mode: "onChange",
|
||||||
|
});
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
control,
|
|
||||||
formState: { isValid, errors },
|
formState: { isValid, errors },
|
||||||
watch,
|
watch,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
clearErrors,
|
clearErrors,
|
||||||
setError,
|
setError,
|
||||||
} = useForm<CredentialsForm>({
|
} = form;
|
||||||
defaultValues: credFormDefaultValues,
|
|
||||||
mode: "onChange",
|
|
||||||
});
|
|
||||||
|
|
||||||
const [confirm, toggle] = useToggle(true);
|
const [confirm, toggle] = useToggle(true);
|
||||||
const password = watch("password", "");
|
const password = watch("password", "");
|
||||||
|
@ -201,32 +199,14 @@ export const ResetPasswordDialog = ({
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup
|
<FormProvider {...form}>
|
||||||
label={t("temporaryPassword")}
|
<DefaultSwitchControl
|
||||||
labelIcon={
|
|
||||||
<HelpItem
|
|
||||||
helpText={t("temporaryPasswordHelpText")}
|
|
||||||
fieldLabelId="temporaryPassword"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
fieldId="kc-temporaryPassword"
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
name="temporaryPassword"
|
name="temporaryPassword"
|
||||||
defaultValue={true}
|
label={t("temporaryPassword")}
|
||||||
control={control}
|
labelIcon={t("temporaryPasswordHelpText")}
|
||||||
render={({ field }) => (
|
defaultValue="true"
|
||||||
<Switch
|
|
||||||
className="kc-temporaryPassword"
|
|
||||||
onChange={field.onChange}
|
|
||||||
isChecked={field.value}
|
|
||||||
label={t("on")}
|
|
||||||
labelOff={t("off")}
|
|
||||||
aria-label={t("temporaryPassword")}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</FormProvider>
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
</Form>
|
||||||
</ConfirmDialogModal>
|
</ConfirmDialogModal>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -18,6 +18,7 @@ export type TextControlProps<
|
||||||
label: string;
|
label: string;
|
||||||
labelIcon?: string;
|
labelIcon?: string;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
|
helperText?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const TextControl = <
|
export const TextControl = <
|
||||||
|
@ -42,6 +43,7 @@ export const TextControl = <
|
||||||
labelIcon={labelIcon}
|
labelIcon={labelIcon}
|
||||||
isRequired={required}
|
isRequired={required}
|
||||||
error={fieldState.error}
|
error={fieldState.error}
|
||||||
|
helperText={props.helperText}
|
||||||
>
|
>
|
||||||
<KeycloakTextInput
|
<KeycloakTextInput
|
||||||
isRequired={required}
|
isRequired={required}
|
||||||
|
|
Loading…
Reference in a new issue