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 #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;
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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!,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
|
|
|
@ -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}
|
||||
|
|
Loading…
Reference in a new issue