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