import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation"; import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation"; import { ActionGroup, AlertVariant, Button, Chip, ChipGroup, FormGroup, InputGroup, Switch, } from "@patternfly/react-core"; import { useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { HelpItem } from "ui-shared"; import { adminClient } from "../admin-client"; import { useAlerts } from "../components/alert/Alerts"; import { FormAccess } from "../components/form/FormAccess"; import { GroupPickerDialog } from "../components/group/GroupPickerDialog"; import { KeycloakTextInput } from "../components/keycloak-text-input/KeycloakTextInput"; import { useAccess } from "../context/access/Access"; import { useRealm } from "../context/realm-context/RealmContext"; import { emailRegexPattern } from "../util"; import useFormatDate from "../utils/useFormatDate"; import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled"; import { FederatedUserLink } from "./FederatedUserLink"; import { UserProfileFields } from "./UserProfileFields"; import { UserFormFields } from "./form-state"; import { RequiredActionMultiSelect } from "./user-credentials/RequiredActionMultiSelect"; export type BruteForced = { isBruteForceProtected?: boolean; isLocked?: boolean; }; export type UserFormProps = { user?: UserRepresentation; bruteForce?: BruteForced; realm?: RealmRepresentation; save: (user: UserFormFields) => void; onGroupsUpdate?: (groups: GroupRepresentation[]) => void; }; const EmailVerified = () => { const { t } = useTranslation("users"); const { control } = useFormContext(); return ( } > ( field.onChange(value)} isChecked={field.value} label={t("common:yes")} labelOff={t("common:no")} /> )} /> ); }; export const UserForm = ({ user, realm, bruteForce: { isBruteForceProtected, isLocked } = { isBruteForceProtected: false, isLocked: false, }, save, onGroupsUpdate, }: UserFormProps) => { const { t } = useTranslation("users"); const { realm: realmName } = useRealm(); const formatDate = useFormatDate(); const isFeatureEnabled = useIsFeatureEnabled(); const navigate = useNavigate(); const { addAlert, addError } = useAlerts(); const { hasAccess } = useAccess(); const isManager = hasAccess("manage-users"); const canViewFederationLink = hasAccess("view-realm"); const { handleSubmit, register, watch, control, reset, formState: { errors }, } = useFormContext(); const watchUsernameInput = watch("username"); const [selectedGroups, setSelectedGroups] = useState( [], ); const [open, setOpen] = useState(false); const [locked, setLocked] = useState(isLocked); const unLockUser = async () => { try { await adminClient.attackDetection.del({ id: user!.id! }); addAlert(t("unlockSuccess"), AlertVariant.success); } catch (error) { addError("users:unlockError", error); } }; const deleteItem = (id: string) => { setSelectedGroups(selectedGroups.filter((item) => item.name !== id)); onGroupsUpdate?.(selectedGroups); }; const addChips = async (groups: GroupRepresentation[]): Promise => { setSelectedGroups([...selectedGroups!, ...groups]); onGroupsUpdate?.([...selectedGroups!, ...groups]); }; const addGroups = async (groups: GroupRepresentation[]): Promise => { const newGroups = groups; newGroups.forEach(async (group) => { try { await adminClient.users.addToGroup({ id: user!.id!, groupId: group.id!, }); addAlert(t("users:addedGroupMembership"), AlertVariant.success); } catch (error) { addError("users:addedGroupMembershipError", error); } }); }; const toggleModal = () => { setOpen(!open); }; const isUserProfileEnabled = isFeatureEnabled(Feature.DeclarativeUserProfile) && realm?.attributes?.userProfileEnabled === "true"; return ( {open && ( { user?.id ? addGroups(groups || []) : addChips(groups || []); setOpen(false); }} onClose={() => setOpen(false)} filterGroups={selectedGroups} /> )} {isUserProfileEnabled && } {user?.id && ( <> )} {(user?.federationLink || user?.origin) && canViewFederationLink && ( } > )} {isUserProfileEnabled ? ( ) : ( <> {!realm?.registrationEmailAsUsername && ( )} )} {isBruteForceProtected && ( } > { unLockUser(); setLocked(value); }} isChecked={locked} isDisabled={!locked} label={t("common:on")} labelOff={t("common:off")} /> )} {!user?.id && ( } > ( {selectedGroups.map((currentChip) => ( deleteItem(currentChip.name!)} > {currentChip.path} ))} )} /> )} ); };