Refactor + attributes with options rendered by default as select inputs
This commit is contained in:
@ -130,20 +130,15 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
const initialState = useMemo((): internal.State => {
|
const initialState = useMemo((): internal.State => {
|
||||||
// NOTE: We don't use te kcContext.profile.attributes directly because
|
// NOTE: We don't use te kcContext.profile.attributes directly because
|
||||||
// they don't includes the password and password confirm fields and we want to add them.
|
// they don't includes the password and password confirm fields and we want to add them.
|
||||||
// Also, we want to polyfill the attributes for older Keycloak version before User Profile was introduced.
|
// We also want to apply some retro-compatibility and consistency patches.
|
||||||
// Finally we want to patch the changes made by Keycloak on the attributes format so we have an homogeneous
|
const attributes: Attribute[] = (() => {
|
||||||
// attributes format to work with.
|
mock_user_profile_attributes_for_older_keycloak_versions: {
|
||||||
const syntheticAttributes = (() => {
|
|
||||||
const syntheticAttributes: Attribute[] = [];
|
|
||||||
|
|
||||||
const attributes = (() => {
|
|
||||||
retrocompat_patch: {
|
|
||||||
if (
|
if (
|
||||||
"profile" in kcContext &&
|
"profile" in kcContext &&
|
||||||
"attributesByName" in kcContext.profile &&
|
"attributesByName" in kcContext.profile &&
|
||||||
Object.keys(kcContext.profile.attributesByName).length !== 0
|
Object.keys(kcContext.profile.attributesByName).length !== 0
|
||||||
) {
|
) {
|
||||||
break retrocompat_patch;
|
break mock_user_profile_attributes_for_older_keycloak_versions;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("register" in kcContext && kcContext.register instanceof Object && "formData" in kcContext.register) {
|
if ("register" in kcContext && kcContext.register instanceof Object && "formData" in kcContext.register) {
|
||||||
@ -222,76 +217,50 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
assert(false, "Unable to mock user profile from the current kcContext");
|
assert(false, "Unable to mock user profile from the current kcContext");
|
||||||
}
|
}
|
||||||
|
|
||||||
return Object.values(kcContext.profile.attributesByName).map(attribute_pre_group_patch => {
|
return Object.values(kcContext.profile.attributesByName).map(structuredCloneButFunctions);
|
||||||
if (typeof attribute_pre_group_patch.group === "string" && attribute_pre_group_patch.group !== "") {
|
})();
|
||||||
const { group, groupDisplayHeader, groupDisplayDescription, groupAnnotations, ...rest } =
|
|
||||||
attribute_pre_group_patch as Attribute & {
|
// Retro-compatibility and consistency patches
|
||||||
|
attributes.forEach(attribute => {
|
||||||
|
patch_legacy_group: {
|
||||||
|
if (typeof attribute.group !== "string") {
|
||||||
|
break patch_legacy_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { group, groupDisplayHeader, groupDisplayDescription /*, groupAnnotations*/ } = attribute as Attribute & {
|
||||||
group: string;
|
group: string;
|
||||||
groupDisplayHeader?: string;
|
groupDisplayHeader?: string;
|
||||||
groupDisplayDescription?: string;
|
groupDisplayDescription?: string;
|
||||||
groupAnnotations: Record<string, string>;
|
groupAnnotations: Record<string, string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
return id<Attribute>({
|
delete attribute.group;
|
||||||
...rest,
|
// @ts-expect-error
|
||||||
group: {
|
delete attribute.groupDisplayHeader;
|
||||||
|
// @ts-expect-error
|
||||||
|
delete attribute.groupDisplayDescription;
|
||||||
|
// @ts-expect-error
|
||||||
|
delete attribute.groupAnnotations;
|
||||||
|
|
||||||
|
if (group === "") {
|
||||||
|
break patch_legacy_group;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute.group = {
|
||||||
name: group,
|
name: group,
|
||||||
displayHeader: groupDisplayHeader,
|
displayHeader: groupDisplayHeader,
|
||||||
displayDescription: groupDisplayDescription,
|
displayDescription: groupDisplayDescription,
|
||||||
html5DataAnnotations: {}
|
html5DataAnnotations: {}
|
||||||
}
|
};
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return attribute_pre_group_patch;
|
// Attributes with options rendered by default as select inputs
|
||||||
});
|
if (attribute.validators.options !== undefined && attribute.annotations.inputType === undefined) {
|
||||||
})();
|
attribute.annotations.inputType = "select";
|
||||||
|
|
||||||
for (const attribute of attributes) {
|
|
||||||
syntheticAttributes.push(structuredCloneButFunctions(attribute));
|
|
||||||
|
|
||||||
add_password_and_password_confirm: {
|
|
||||||
if (!kcContext.passwordRequired) {
|
|
||||||
break add_password_and_password_confirm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attribute.name !== (kcContext.realm.registrationEmailAsUsername ? "email" : "username")) {
|
// Consistency patch on values/value property
|
||||||
// NOTE: We want to add password and password-confirm after the field that identifies the user.
|
|
||||||
// It's either email or username.
|
|
||||||
break add_password_and_password_confirm;
|
|
||||||
}
|
|
||||||
|
|
||||||
syntheticAttributes.push(
|
|
||||||
{
|
{
|
||||||
name: "password",
|
|
||||||
displayName: id<`\${${MessageKey}}`>("${password}"),
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
validators: {},
|
|
||||||
annotations: {},
|
|
||||||
autocomplete: "new-password",
|
|
||||||
html5DataAnnotations: {},
|
|
||||||
// NOTE: Compat with Keycloak version prior to 24
|
|
||||||
...({ groupAnnotations: {} } as {})
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "password-confirm",
|
|
||||||
displayName: id<`\${${MessageKey}}`>("${passwordConfirm}"),
|
|
||||||
required: true,
|
|
||||||
readOnly: false,
|
|
||||||
validators: {},
|
|
||||||
annotations: {},
|
|
||||||
html5DataAnnotations: {},
|
|
||||||
autocomplete: "new-password",
|
|
||||||
// NOTE: Compat with Keycloak version prior to 24
|
|
||||||
...({ groupAnnotations: {} } as {})
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Consistency patch
|
|
||||||
syntheticAttributes.forEach(attribute => {
|
|
||||||
if (getIsMultivaluedSingleField({ attribute })) {
|
if (getIsMultivaluedSingleField({ attribute })) {
|
||||||
attribute.multivalued = true;
|
attribute.multivalued = true;
|
||||||
}
|
}
|
||||||
@ -303,18 +272,54 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
attribute.value ??= attribute.values?.[0];
|
attribute.value ??= attribute.values?.[0];
|
||||||
delete attribute.values;
|
delete attribute.values;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return syntheticAttributes;
|
add_password_and_password_confirm: {
|
||||||
})();
|
if (!kcContext.passwordRequired) {
|
||||||
|
break add_password_and_password_confirm;
|
||||||
|
}
|
||||||
|
|
||||||
const initialFormFieldState = (() => {
|
attributes.forEach((attribute, i) => {
|
||||||
const out: {
|
if (attribute.name !== (kcContext.realm.registrationEmailAsUsername ? "email" : "username")) {
|
||||||
|
// NOTE: We want to add password and password-confirm after the field that identifies the user.
|
||||||
|
// It's either email or username.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
attributes.splice(
|
||||||
|
i + 1,
|
||||||
|
0,
|
||||||
|
{
|
||||||
|
name: "password",
|
||||||
|
displayName: id<`\${${MessageKey}}`>("${password}"),
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
validators: {},
|
||||||
|
annotations: {},
|
||||||
|
autocomplete: "new-password",
|
||||||
|
html5DataAnnotations: {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "password-confirm",
|
||||||
|
displayName: id<`\${${MessageKey}}`>("${passwordConfirm}"),
|
||||||
|
required: true,
|
||||||
|
readOnly: false,
|
||||||
|
validators: {},
|
||||||
|
annotations: {},
|
||||||
|
html5DataAnnotations: {},
|
||||||
|
autocomplete: "new-password"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialFormFieldState: {
|
||||||
attribute: Attribute;
|
attribute: Attribute;
|
||||||
valueOrValues: string | string[];
|
valueOrValues: string | string[];
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
for (const attribute of syntheticAttributes) {
|
for (const attribute of attributes) {
|
||||||
handle_multi_valued_attribute: {
|
handle_multi_valued_attribute: {
|
||||||
if (!attribute.multivalued) {
|
if (!attribute.multivalued) {
|
||||||
break handle_multi_valued_attribute;
|
break handle_multi_valued_attribute;
|
||||||
@ -346,7 +351,7 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out.push({
|
initialFormFieldState.push({
|
||||||
attribute,
|
attribute,
|
||||||
valueOrValues: values
|
valueOrValues: values
|
||||||
});
|
});
|
||||||
@ -354,15 +359,12 @@ export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTy
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
out.push({
|
initialFormFieldState.push({
|
||||||
attribute,
|
attribute,
|
||||||
valueOrValues: attribute.value ?? ""
|
valueOrValues: attribute.value ?? ""
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return out;
|
|
||||||
})();
|
|
||||||
|
|
||||||
const initialState: internal.State = {
|
const initialState: internal.State = {
|
||||||
formFieldStates: initialFormFieldState.map(({ attribute, valueOrValues }) => ({
|
formFieldStates: initialFormFieldState.map(({ attribute, valueOrValues }) => ({
|
||||||
attribute,
|
attribute,
|
||||||
|
Reference in New Issue
Block a user