Centralize user form to user representation conversion (#22851)
This commit is contained in:
parent
28c2cba3b9
commit
bb4495adb7
5 changed files with 51 additions and 49 deletions
|
@ -1,5 +1,4 @@
|
|||
import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/groupRepresentation";
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import { AlertVariant, PageSection } from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
|
@ -16,6 +15,7 @@ import {
|
|||
isUserProfileError,
|
||||
userProfileErrorToString,
|
||||
} from "./UserProfileFields";
|
||||
import { UserFormFields, toUserRepresentation } from "./form-state";
|
||||
import { toUser } from "./routes/User";
|
||||
|
||||
import "./user-section.css";
|
||||
|
@ -25,14 +25,13 @@ export default function CreateUser() {
|
|||
const { addAlert, addError } = useAlerts();
|
||||
const navigate = useNavigate();
|
||||
const { realm } = useRealm();
|
||||
const userForm = useForm<UserRepresentation>({ mode: "onChange" });
|
||||
const userForm = useForm<UserFormFields>({ mode: "onChange" });
|
||||
const [addedGroups, setAddedGroups] = useState<GroupRepresentation[]>([]);
|
||||
|
||||
const save = async (formUser: UserRepresentation) => {
|
||||
const save = async (data: UserFormFields) => {
|
||||
try {
|
||||
const createdUser = await adminClient.users.create({
|
||||
...formUser,
|
||||
username: formUser.username?.trim(),
|
||||
...toUserRepresentation(data),
|
||||
groups: addedGroups.map((group) => group.path!),
|
||||
enabled: true,
|
||||
});
|
||||
|
|
|
@ -15,10 +15,6 @@ import { useNavigate } from "react-router-dom";
|
|||
import { adminClient } from "../admin-client";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import {
|
||||
KeyValueType,
|
||||
keyValueToArray,
|
||||
} from "../components/key-value-form/key-value-convert";
|
||||
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
|
||||
import {
|
||||
RoutableTabs,
|
||||
|
@ -44,6 +40,11 @@ import {
|
|||
} from "./UserProfileFields";
|
||||
import { UserRoleMapping } from "./UserRoleMapping";
|
||||
import { UserSessions } from "./UserSessions";
|
||||
import {
|
||||
UserFormFields,
|
||||
toUserFormFields,
|
||||
toUserRepresentation,
|
||||
} from "./form-state";
|
||||
import { UserParams, UserTab, toUser } from "./routes/User";
|
||||
import { toUsers } from "./routes/Users";
|
||||
|
||||
|
@ -97,17 +98,15 @@ type EditUserFormProps = {
|
|||
refresh: () => void;
|
||||
};
|
||||
|
||||
type FormFields = Omit<UserRepresentation, "userProfileMetadata">;
|
||||
|
||||
const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => {
|
||||
const { t } = useTranslation("users");
|
||||
const { realm } = useRealm();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const navigate = useNavigate();
|
||||
const { hasAccess } = useAccess();
|
||||
const userForm = useForm<FormFields>({
|
||||
const userForm = useForm<UserFormFields>({
|
||||
mode: "onChange",
|
||||
defaultValues: user,
|
||||
defaultValues: toUserFormFields(user),
|
||||
});
|
||||
|
||||
const [realmRepresentation, setRealmRepresentattion] =
|
||||
|
@ -148,22 +147,13 @@ const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => {
|
|||
const sessionsTab = useTab("sessions");
|
||||
|
||||
// Ensure the form remains up-to-date when the user is updated.
|
||||
useUpdateEffect(() => userForm.reset(user), [user]);
|
||||
useUpdateEffect(() => userForm.reset(toUserFormFields(user)), [user]);
|
||||
|
||||
const save = async (formUser: UserRepresentation) => {
|
||||
const attributes =
|
||||
"key" in (formUser.attributes?.[0] || [])
|
||||
? keyValueToArray(formUser.attributes as KeyValueType[])
|
||||
: [];
|
||||
const save = async (data: UserFormFields) => {
|
||||
try {
|
||||
await adminClient.users.update(
|
||||
{ id: user.id! },
|
||||
{
|
||||
...user,
|
||||
...formUser,
|
||||
username: formUser.username?.trim(),
|
||||
attributes,
|
||||
},
|
||||
toUserRepresentation(data),
|
||||
);
|
||||
addAlert(t("userSaved"), AlertVariant.success);
|
||||
refresh();
|
||||
|
@ -237,7 +227,9 @@ const EditUserForm = ({ user, bruteForced, refresh }: EditUserFormProps) => {
|
|||
{t("common:delete")}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
onToggle={(value) => save({ ...user, enabled: value })}
|
||||
onToggle={(value) =>
|
||||
save({ ...toUserFormFields(user), enabled: value })
|
||||
}
|
||||
isEnabled={user.enabled}
|
||||
/>
|
||||
|
||||
|
|
|
@ -1,31 +1,17 @@
|
|||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import { PageSection, PageSectionVariants } from "@patternfly/react-core";
|
||||
import { useEffect } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
|
||||
import {
|
||||
AttributeForm,
|
||||
AttributesForm,
|
||||
} from "../components/key-value-form/AttributeForm";
|
||||
import { arrayToKeyValue } from "../components/key-value-form/key-value-convert";
|
||||
import { useUserProfile } from "../realm-settings/user-profile/UserProfileContext";
|
||||
import { AttributesForm } from "../components/key-value-form/AttributeForm";
|
||||
import { UserFormFields, toUserFormFields } from "./form-state";
|
||||
|
||||
type UserAttributesProps = {
|
||||
user: UserRepresentation;
|
||||
save: (user: UserRepresentation) => void;
|
||||
save: (user: UserFormFields) => void;
|
||||
};
|
||||
|
||||
export const UserAttributes = ({ user, save }: UserAttributesProps) => {
|
||||
const form = useForm<AttributeForm>({ mode: "onChange" });
|
||||
const { config } = useUserProfile();
|
||||
|
||||
const convertAttributes = () => {
|
||||
return arrayToKeyValue<UserRepresentation>(user.attributes!);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue("attributes", convertAttributes());
|
||||
}, [user, config]);
|
||||
const form = useFormContext<UserFormFields>();
|
||||
|
||||
return (
|
||||
<PageSection variant={PageSectionVariants.light}>
|
||||
|
@ -35,7 +21,8 @@ export const UserAttributes = ({ user, save }: UserAttributesProps) => {
|
|||
fineGrainedAccess={user.access?.manage}
|
||||
reset={() =>
|
||||
form.reset({
|
||||
attributes: convertAttributes(),
|
||||
...form.getValues(),
|
||||
attributes: toUserFormFields(user).attributes,
|
||||
})
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -29,6 +29,7 @@ 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 = {
|
||||
|
@ -40,13 +41,13 @@ export type UserFormProps = {
|
|||
user?: UserRepresentation;
|
||||
bruteForce?: BruteForced;
|
||||
realm?: RealmRepresentation;
|
||||
save: (user: UserRepresentation) => void;
|
||||
save: (user: UserFormFields) => void;
|
||||
onGroupsUpdate?: (groups: GroupRepresentation[]) => void;
|
||||
};
|
||||
|
||||
const EmailVerified = () => {
|
||||
const { t } = useTranslation("users");
|
||||
const { control } = useFormContext();
|
||||
const { control } = useFormContext<UserFormFields>();
|
||||
return (
|
||||
<FormGroup
|
||||
label={t("emailVerified")}
|
||||
|
@ -106,7 +107,7 @@ export const UserForm = ({
|
|||
control,
|
||||
reset,
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
} = useFormContext<UserFormFields>();
|
||||
const watchUsernameInput = watch("username");
|
||||
const [selectedGroups, setSelectedGroups] = useState<GroupRepresentation[]>(
|
||||
[],
|
||||
|
|
23
js/apps/admin-ui/src/user/form-state.ts
Normal file
23
js/apps/admin-ui/src/user/form-state.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import {
|
||||
KeyValueType,
|
||||
arrayToKeyValue,
|
||||
keyValueToArray,
|
||||
} from "../components/key-value-form/key-value-convert";
|
||||
|
||||
export type UserFormFields = Omit<UserRepresentation, "userProfileMetadata"> & {
|
||||
attributes?: KeyValueType[];
|
||||
};
|
||||
|
||||
export function toUserFormFields(data: UserRepresentation): UserFormFields {
|
||||
const attributes = arrayToKeyValue(data.attributes);
|
||||
|
||||
return { ...data, attributes };
|
||||
}
|
||||
|
||||
export function toUserRepresentation(data: UserFormFields): UserRepresentation {
|
||||
const username = data.username?.trim();
|
||||
const attributes = keyValueToArray(data.attributes);
|
||||
|
||||
return { ...data, username, attributes };
|
||||
}
|
Loading…
Reference in a new issue