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:
Erik Jan de Wit 2024-03-06 17:06:13 +01:00 committed by GitHub
parent 39299eeb38
commit b0b967d8d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 284 additions and 451 deletions

View file

@ -6,16 +6,16 @@ export default class CredentialsPage {
readonly #setPasswordBtn = "confirm";
readonly #credentialResetModal = "credential-reset-modal";
readonly #resetModalActionsToggleBtn =
"[data-testid=credential-reset-modal] #actions-actions";
"[data-testid=credential-reset-modal] #actions";
readonly #passwordField = "passwordField";
readonly #passwordConfirmationField = "passwordConfirmationField";
readonly #resetActions = [
"VERIFY_EMAIL-option",
"UPDATE_PROFILE-option",
"CONFIGURE_TOTP-option",
"UPDATE_PASSWORD-option",
"TERMS_AND_CONDITIONS-option",
"Verify Email",
"Update Profile",
"Configure OTP",
"Update Password",
"Terms and Conditions",
];
readonly #confirmationButton = "confirm";
readonly #editLabelBtn = "editUserLabelBtn";
@ -57,7 +57,9 @@ export default class CredentialsPage {
}
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;
}

View file

@ -9,8 +9,8 @@ export default class IdentityProviderLinksTab {
#availableProvidersSection = ".kc-available-idps";
#linkAccountBtn = ".pf-c-button.pf-m-link";
#linkAccountModalIdentityProviderInput = "idpNameInput";
#linkAccountModalUserIdInput = "userIdInput";
#linkAccountModalUsernameInput = "usernameInput";
#linkAccountModalUserIdInput = "userId";
#linkAccountModalUsernameInput = "userName";
public clickLinkAccount(idpName: string) {
cy.get(this.#availableProvidersSection + " tr")

View file

@ -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.
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
resetAction=Reset action
cibaExpiresIn=Expires In
dynamicScopeFormatHelp=This is the regular expression that the system will use to extract the scope name and variable.
updateTranslationError=Error updating translation.

View file

@ -13,13 +13,18 @@ import {
Switch,
} from "@patternfly/react-core";
import { TFunction } from "i18next";
import { useState, useEffect } from "react";
import { Controller, UseFormReturn } from "react-hook-form";
import { useEffect, useState } from "react";
import { Controller, FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
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 { DefaultSwitchControl } from "../components/SwitchControl";
import { useAlerts } from "../components/alert/Alerts";
import { FormAccess } from "../components/form/FormAccess";
import { GroupPickerDialog } from "../components/group/GroupPickerDialog";
@ -68,7 +73,6 @@ export const UserForm = ({
const {
handleSubmit,
register,
setValue,
watch,
control,
@ -133,284 +137,214 @@ export const UserForm = ({
fineGrainedAccess={user?.access?.manage}
className="pf-u-mt-lg"
>
{open && (
<GroupPickerDialog
type="selectMany"
text={{
title: "selectGroups",
ok: "join",
}}
canBrowse={isManager}
onConfirm={(groups) => {
user?.id ? addGroups(groups || []) : addChips(groups || []);
setOpen(false);
}}
onClose={() => setOpen(false)}
filterGroups={selectedGroups}
<FormProvider {...form}>
{open && (
<GroupPickerDialog
type="selectMany"
text={{
title: "selectGroups",
ok: "join",
}}
canBrowse={isManager}
onConfirm={(groups) => {
user?.id ? addGroups(groups || []) : addChips(groups || []);
setOpen(false);
}}
onClose={() => setOpen(false)}
filterGroups={selectedGroups}
/>
)}
{user?.id && (
<>
<FormGroup label={t("id")} fieldId="kc-id" isRequired>
<KeycloakTextInput
id={user.id}
aria-label={t("userID")}
value={user.id}
readOnly
/>
</FormGroup>
<FormGroup
label={t("createdAt")}
fieldId="kc-created-at"
isRequired
>
<KeycloakTextInput
value={formatDate(new Date(user.createdTimestamp!))}
id="kc-created-at"
readOnly
/>
</FormGroup>
</>
)}
<RequiredActionMultiSelect
name="requiredActions"
label="requiredUserActions"
help="requiredUserActionsHelp"
/>
)}
{user?.id && (
<>
<FormGroup label={t("id")} fieldId="kc-id" isRequired>
<KeycloakTextInput
id={user.id}
aria-label={t("userID")}
value={user.id}
type="text"
isReadOnly
/>
</FormGroup>
<FormGroup label={t("createdAt")} fieldId="kc-created-at" isRequired>
<KeycloakTextInput
value={formatDate(new Date(user.createdTimestamp!))}
type="text"
id="kc-created-at"
aria-label={t("createdAt")}
name="createdTimestamp"
isReadOnly
/>
</FormGroup>
</>
)}
<RequiredActionMultiSelect
control={control}
name="requiredActions"
label="requiredUserActions"
help="requiredUserActionsHelp"
/>
{(user?.federationLink || user?.origin) && canViewFederationLink && (
<FormGroup
label={t("federationLink")}
labelIcon={
<HelpItem
helpText={t("federationLinkHelp")}
fieldLabelId="federationLink"
/>
}
>
<FederatedUserLink user={user} />
</FormGroup>
)}
{userProfileMetadata ? (
<>
{(user?.federationLink || user?.origin) && canViewFederationLink && (
<FormGroup
label={t("emailVerified")}
fieldId="kc-email-verified"
helperTextInvalid={t("required")}
label={t("federationLink")}
labelIcon={
<HelpItem
helpText={t("emailVerifiedHelp")}
fieldLabelId="emailVerified"
helpText={t("federationLinkHelp")}
fieldLabelId="federationLink"
/>
}
>
<Controller
name="emailVerified"
defaultValue={false}
control={control}
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")}
/>
)}
/>
<FederatedUserLink user={user} />
</FormGroup>
<UserProfileFields
form={form}
userProfileMetadata={userProfileMetadata}
hideReadOnly={!user}
supportedLocales={realm.supportedLocales || []}
t={
((key: unknown, params) =>
t(key as string, params as any)) as TFunction
}
/>
</>
) : (
<>
{!realm.registrationEmailAsUsername && (
<FormGroup
label={t("username")}
fieldId="kc-username"
isRequired
validated={errors.username ? "error" : "default"}
helperTextInvalid={t("required")}
>
<KeycloakTextInput
id="kc-username"
isReadOnly={
)}
{userProfileMetadata ? (
<>
<DefaultSwitchControl
name="emailVerified"
label={t("emailVerified")}
labelIcon={t("emailVerifiedHelp")}
/>
<UserProfileFields
form={form}
userProfileMetadata={userProfileMetadata}
hideReadOnly={!user}
supportedLocales={realm.supportedLocales || []}
t={
((key: unknown, params) =>
t(key as string, params as any)) as TFunction
}
/>
</>
) : (
<>
{!realm.registrationEmailAsUsername && (
<TextControl
name="username"
label={t("username")}
readOnly={
!!user?.id &&
!realm.editUsernameAllowed &&
realm.editUsernameAllowed !== undefined
}
{...register("username")}
rules={{
required: t("required"),
}}
/>
</FormGroup>
)}
<FormGroup
label={t("email")}
fieldId="kc-email"
validated={errors.email ? "error" : "default"}
helperTextInvalid={t("emailInvalid")}
>
<KeycloakTextInput
)}
<TextControl
name="email"
label={t("email")}
type="email"
id="kc-email"
data-testid="email-input"
{...register("email", {
pattern: emailRegexPattern,
})}
rules={{
pattern: {
value: emailRegexPattern,
message: t("emailInvalid"),
},
}}
/>
</FormGroup>
<SwitchControl
name="emailVerified"
label={t("emailVerified")}
labelIcon={t("emailVerifiedHelp")}
labelOn={t("yes")}
labelOff={t("no")}
/>
<TextControl name="firstName" label={t("firstName")} />
<TextControl name="lastName" label={t("lastName")} />
</>
)}
{isBruteForceProtected && (
<FormGroup
label={t("emailVerified")}
fieldId="kc-email-verified"
helperTextInvalid={t("required")}
label={t("temporaryLocked")}
fieldId="temporaryLocked"
labelIcon={
<HelpItem
helpText={t("emailVerifiedHelp")}
fieldLabelId="emailVerified"
helpText={t("temporaryLockedHelp")}
fieldLabelId="temporaryLocked"
/>
}
>
<Switch
data-testid="user-locked-switch"
id="temporaryLocked"
onChange={(value) => {
unLockUser();
setLocked(value);
}}
isChecked={locked}
isDisabled={!locked}
label={t("on")}
labelOff={t("off")}
/>
</FormGroup>
)}
{!user?.id && (
<FormGroup
label={t("groups")}
fieldId="kc-groups"
validated={errors.requiredActions ? "error" : "default"}
helperTextInvalid={t("required")}
labelIcon={
<HelpItem helpText={t("groups")} fieldLabelId="groups" />
}
>
<Controller
name="emailVerified"
defaultValue={false}
name="groups"
defaultValue={[]}
control={control}
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")}
/>
render={() => (
<InputGroup>
<ChipGroup categoryName={" "}>
{selectedGroups.map((currentChip) => (
<Chip
key={currentChip.id}
onClick={() => deleteItem(currentChip.name!)}
>
{currentChip.path}
</Chip>
))}
</ChipGroup>
<Button
id="kc-join-groups-button"
onClick={toggleModal}
variant="secondary"
data-testid="join-groups-button"
>
{t("joinGroups")}
</Button>
</InputGroup>
)}
/>
</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 && (
<FormGroup
label={t("temporaryLocked")}
fieldId="temporaryLocked"
labelIcon={
<HelpItem
helpText={t("temporaryLockedHelp")}
fieldLabelId="temporaryLocked"
/>
}
>
<Switch
data-testid="user-locked-switch"
id="temporaryLocked"
onChange={(value) => {
unLockUser();
setLocked(value);
}}
isChecked={locked}
isDisabled={!locked}
label={t("on")}
labelOff={t("off")}
/>
</FormGroup>
)}
{!user?.id && (
<FormGroup
label={t("groups")}
fieldId="kc-groups"
validated={errors.requiredActions ? "error" : "default"}
helperTextInvalid={t("required")}
labelIcon={<HelpItem helpText={t("groups")} fieldLabelId="groups" />}
>
<Controller
name="groups"
defaultValue={[]}
control={control}
render={() => (
<InputGroup>
<ChipGroup categoryName={" "}>
{selectedGroups.map((currentChip) => (
<Chip
key={currentChip.id}
onClick={() => deleteItem(currentChip.name!)}
>
{currentChip.path}
</Chip>
))}
</ChipGroup>
<Button
id="kc-join-groups-button"
onClick={toggleModal}
variant="secondary"
data-testid="join-groups-button"
>
{t("joinGroups")}
</Button>
</InputGroup>
)}
/>
</FormGroup>
)}
)}
<ActionGroup>
<Button
data-testid={!user?.id ? "create-user" : "save-user"}
isDisabled={
!user?.id &&
!watchUsernameInput &&
realm.registrationEmailAsUsername === false
}
variant="primary"
type="submit"
>
{user?.id ? t("save") : t("create")}
</Button>
<Button
data-testid="cancel-create-user"
variant="link"
onClick={user?.id ? () => reset(toUserFormFields(user)) : undefined}
component={
!user?.id
? (props) => (
<Link {...props} to={toUsers({ realm: realm.realm! })} />
)
: undefined
}
>
{user?.id ? t("revert") : t("cancel")}
</Button>
</ActionGroup>
<ActionGroup>
<Button
data-testid={!user?.id ? "create-user" : "save-user"}
isDisabled={
!user?.id &&
!watchUsernameInput &&
realm.registrationEmailAsUsername === false
}
variant="primary"
type="submit"
>
{user?.id ? t("save") : t("create")}
</Button>
<Button
data-testid="cancel-create-user"
variant="link"
onClick={user?.id ? () => reset(toUserFormFields(user)) : undefined}
component={
!user?.id
? (props) => (
<Link {...props} to={toUsers({ realm: realm.realm! })} />
)
: undefined
}
>
{user?.id ? t("revert") : t("cancel")}
</Button>
</ActionGroup>
</FormProvider>
</FormAccess>
);
};

View file

@ -7,12 +7,11 @@ import {
FormGroup,
Modal,
ModalVariant,
ValidatedOptions,
} from "@patternfly/react-core";
import { capitalize } from "lodash-es";
import { useForm } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { TextControl } from "ui-shared";
import { adminClient } from "../admin-client";
import { useAlerts } from "../components/alert/Alerts";
import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput";
@ -32,13 +31,13 @@ export const UserIdpModal = ({
}: UserIdpModalProps) => {
const { t } = useTranslation();
const { addAlert, addError } = useAlerts();
const {
register,
handleSubmit,
formState: { isValid, errors },
} = useForm<FederatedIdentityRepresentation>({
const form = useForm<FederatedIdentityRepresentation>({
mode: "onChange",
});
const {
handleSubmit,
formState: { isValid },
} = form;
const onSubmit = async (
federatedIdentity: FederatedIdentityRepresentation,
@ -87,55 +86,33 @@ export const UserIdpModal = ({
isOpen
>
<Form id="group-form" onSubmit={handleSubmit(onSubmit)}>
<FormGroup label={t("identityProvider")} fieldId="identityProvider">
<KeycloakTextInput
id="identityProvider"
data-testid="idpNameInput"
value={capitalize(federatedId)}
isReadOnly
/>
</FormGroup>
<FormGroup
label={t("userID")}
fieldId="userID"
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
}
<FormProvider {...form}>
<FormGroup label={t("identityProvider")} fieldId="identityProvider">
<KeycloakTextInput
id="identityProvider"
data-testid="idpNameInput"
value={capitalize(federatedId)}
readOnly
/>
</FormGroup>
<TextControl
name="userId"
label={t("userID")}
helperText={t("userIdHelperText")}
autoFocus
{...register("userId", { required: true })}
rules={{
required: t("required"),
}}
/>
</FormGroup>
<FormGroup
label={t("username")}
fieldId="username"
helperText={t("usernameHelperText")}
helperTextInvalid={t("required")}
validated={
errors.userName ? ValidatedOptions.error : ValidatedOptions.default
}
isRequired
>
<KeycloakTextInput
id="username"
data-testid="usernameInput"
validated={
errors.userName
? ValidatedOptions.error
: ValidatedOptions.default
}
{...register("userName", { required: true })}
<TextControl
name="userName"
label={t("username")}
helperText={t("usernameHelperText")}
rules={{
required: t("required"),
}}
/>
</FormGroup>
</FormProvider>
</Form>
</Modal>
);

View file

@ -1,37 +1,20 @@
import { FormGroup } from "@patternfly/react-core";
import { Controller, useFormContext } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem } from "ui-shared";
import { TimeSelector } from "../../components/time-selector/TimeSelector";
import { TimeSelectorControl } from "../../components/time-selector/TimeSelectorControl";
import { credResetFormDefaultValues } from "./ResetCredentialDialog";
export const LifespanField = () => {
const { t } = useTranslation();
const { control } = useFormContext();
return (
<FormGroup
fieldId="lifespan"
<TimeSelectorControl
name="lifespan"
label={t("lifespan")}
isStack
labelIcon={
<HelpItem helpText={t("lifespanHelp")} fieldLabelId="lifespan" />
}
>
<Controller
name="lifespan"
defaultValue={credResetFormDefaultValues.lifespan}
control={control}
render={({ field }) => (
<TimeSelector
value={field.value}
units={["minute", "hour", "day"]}
onChange={field.onChange}
menuAppendTo="parent"
/>
)}
/>
</FormGroup>
labelIcon={t("lifespanHelp")}
units={["minute", "hour", "day"]}
menuAppendTo="parent"
controller={{
defaultValue: credResetFormDefaultValues.lifespan,
}}
/>
);
};

View file

@ -1,20 +1,9 @@
import type RequiredActionProviderRepresentation from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
import {
FormGroup,
Select,
SelectOption,
SelectVariant,
} from "@patternfly/react-core";
import { SelectVariant } from "@patternfly/react-core";
import { useState } from "react";
import {
Control,
Controller,
FieldPathByValue,
FieldValues,
PathValue,
} from "react-hook-form";
import { FieldPathByValue, FieldValues } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem } from "ui-shared";
import { SelectControl } from "ui-shared";
import { adminClient } from "../../admin-client";
import { useFetch } from "../../utils/useFetch";
@ -23,7 +12,6 @@ export type RequiredActionMultiSelectProps<
T extends FieldValues,
P extends FieldPathByValue<T, string[] | undefined>,
> = {
control: Control<T>;
name: P;
label: string;
help: string;
@ -33,13 +21,11 @@ export const RequiredActionMultiSelect = <
T extends FieldValues,
P extends FieldPathByValue<T, string[] | undefined>,
>({
control,
name,
label,
help,
}: RequiredActionMultiSelectProps<T, P>) => {
const { t } = useTranslation();
const [open, setOpen] = useState(false);
const [requiredActions, setRequiredActions] = useState<
RequiredActionProviderRepresentation[]
>([]);
@ -56,54 +42,23 @@ export const RequiredActionMultiSelect = <
);
return (
<FormGroup
<SelectControl
name={name}
label={t(label)}
labelIcon={<HelpItem helpText={t(help)} fieldLabelId="resetAction" />}
fieldId="actions"
>
<Controller
name={name}
defaultValue={[] as PathValue<T, P>}
control={control}
render={({ field }) => (
<Select
maxHeight={375}
toggleId={`${name}-actions`}
variant={SelectVariant.typeaheadMulti}
chipGroupProps={{
numChips: 3,
}}
placeholderText={t("requiredActionPlaceholder")}
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")}
>
{requiredActions.map(({ alias, name }) => (
<SelectOption
key={alias}
value={alias}
data-testid={`${alias}-option`}
>
{name}
</SelectOption>
))}
</Select>
)}
/>
</FormGroup>
labelIcon={t(help)}
controller={{ defaultValue: [] }}
maxHeight={375}
variant={SelectVariant.typeaheadMulti}
chipGroupProps={{
numChips: 3,
}}
placeholderText={t("requiredActionPlaceholder")}
menuAppendTo="parent"
typeAheadAriaLabel={t("resetAction")}
options={requiredActions.map(({ alias, name }) => ({
key: alias!,
value: name || alias!,
}))}
/>
);
};

View file

@ -82,13 +82,12 @@ export const ResetCredentialDialog = ({
isHorizontal
data-testid="credential-reset-modal"
>
<RequiredActionMultiSelect
control={control}
name="actions"
label="resetAction"
help="resetActions"
/>
<FormProvider {...form}>
<RequiredActionMultiSelect
name="actions"
label="resetAction"
help="resetActions"
/>
<LifespanField />
</FormProvider>
</Form>

View file

@ -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 type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import {
AlertVariant,
ButtonVariant,
Form,
FormGroup,
Switch,
ValidatedOptions,
} from "@patternfly/react-core";
import { Controller, useForm } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { HelpItem } from "ui-shared";
import { adminClient } from "../../admin-client";
import { DefaultSwitchControl } from "../../components/SwitchControl";
import { useAlerts } from "../../components/alert/Alerts";
import {
ConfirmDialogModal,
@ -49,18 +47,18 @@ export const ResetPasswordDialog = ({
onClose,
}: ResetPasswordDialogProps) => {
const { t } = useTranslation();
const form = useForm<CredentialsForm>({
defaultValues: credFormDefaultValues,
mode: "onChange",
});
const {
register,
control,
formState: { isValid, errors },
watch,
handleSubmit,
clearErrors,
setError,
} = useForm<CredentialsForm>({
defaultValues: credFormDefaultValues,
mode: "onChange",
});
} = form;
const [confirm, toggle] = useToggle(true);
const password = watch("password", "");
@ -201,32 +199,14 @@ export const ResetPasswordDialog = ({
})}
/>
</FormGroup>
<FormGroup
label={t("temporaryPassword")}
labelIcon={
<HelpItem
helpText={t("temporaryPasswordHelpText")}
fieldLabelId="temporaryPassword"
/>
}
fieldId="kc-temporaryPassword"
>
<Controller
<FormProvider {...form}>
<DefaultSwitchControl
name="temporaryPassword"
defaultValue={true}
control={control}
render={({ field }) => (
<Switch
className="kc-temporaryPassword"
onChange={field.onChange}
isChecked={field.value}
label={t("on")}
labelOff={t("off")}
aria-label={t("temporaryPassword")}
/>
)}
label={t("temporaryPassword")}
labelIcon={t("temporaryPasswordHelpText")}
defaultValue="true"
/>
</FormGroup>
</FormProvider>
</Form>
</ConfirmDialogModal>
</>

View file

@ -18,6 +18,7 @@ export type TextControlProps<
label: string;
labelIcon?: string;
isDisabled?: boolean;
helperText?: string;
};
export const TextControl = <
@ -42,6 +43,7 @@ export const TextControl = <
labelIcon={labelIcon}
isRequired={required}
error={fieldState.error}
helperText={props.helperText}
>
<KeycloakTextInput
isRequired={required}