import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig"; import { ActionGroup, ActionList, ActionListItem, Button, Flex, FlexItem, FormGroup, PageSection, Text, TextContent, TextInput, } from "@patternfly/react-core"; import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons"; import React, { useEffect, useMemo } from "react"; import { ArrayField, SubmitHandler, useFieldArray, useForm, useWatch, } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { Link, useHistory, useParams } from "react-router-dom"; import { FormAccess } from "../../components/form-access/FormAccess"; import { HelpItem } from "../../components/help-enabler/HelpItem"; import { ViewHeader } from "../../components/view-header/ViewHeader"; import { useRealm } from "../../context/realm-context/RealmContext"; import "../realm-settings-section.css"; import type { EditAttributesGroupParams } from "../routes/EditAttributesGroup"; import { toUserProfile } from "../routes/UserProfile"; import { useUserProfile } from "./UserProfileContext"; enum AnnotationType { String = "string", Unknown = "unknown", } type StringAnnotation = { type: AnnotationType.String; key: string; value: string; }; type UnknownAnnotation = { type: AnnotationType.Unknown; key: string; value: unknown; }; type Annotation = StringAnnotation | UnknownAnnotation; function parseAnnotations(input: Record) { return Object.entries(input).map(([key, value]) => { if (typeof value === "string") { return { type: AnnotationType.String, key, value }; } return { type: AnnotationType.Unknown, key, value }; }); } function transformAnnotations(input: Annotation[]): Record { return Object.fromEntries( input .filter((annotation) => annotation.key.length > 0) .map((annotation) => [annotation.key, annotation.value] as const) ); } type FormFields = Required> & { annotations: Annotation[]; }; const defaultValues: FormFields = { annotations: [{ type: AnnotationType.String, key: "", value: "" }], displayDescription: "", displayHeader: "", name: "", }; export default function AttributesGroupForm() { const { t } = useTranslation(); const { realm } = useRealm(); const { config, save } = useUserProfile(); const history = useHistory(); const params = useParams>(); const form = useForm({ defaultValues, shouldUnregister: false }); const annotationsField = useFieldArray({ control: form.control, name: "annotations", }); const annotations = useWatch({ control: form.control, name: "annotations", defaultValue: defaultValues.annotations, }); const annotationsValid = annotations .filter( (annotation): annotation is StringAnnotation => annotation.type === AnnotationType.String ) .every( ({ key, value }) => key.trim().length !== 0 && value.trim().length !== 0 ); const matchingGroup = useMemo( () => config?.groups?.find(({ name }) => name === params.name), [config?.groups] ); useEffect(() => { if (!matchingGroup) { return; } const annotations = matchingGroup.annotations ? parseAnnotations(matchingGroup.annotations) : []; if (annotations.length === 0) { annotations.push({ type: AnnotationType.String, key: "", value: "" }); } form.reset({ ...defaultValues, ...matchingGroup, annotations }); }, [matchingGroup]); const onSubmit: SubmitHandler = async (values) => { if (!config) { return; } const groups = [...(config.groups ?? [])]; const updateAt = matchingGroup ? groups.indexOf(matchingGroup) : -1; const updatedGroup: UserProfileGroup = { ...values, annotations: transformAnnotations(values.annotations), }; if (updateAt === -1) { groups.push(updatedGroup); } else { groups[updateAt] = updatedGroup; } const success = await save({ ...config, groups }); if (success) { history.push(toUserProfile({ realm, tab: "attributesGroup" })); } }; function addAnnotation() { annotationsField.append({ type: AnnotationType.String, key: "", value: "", }); } function removeAnnotation(index: number) { annotationsField.remove(index); } return ( <> } > {!!matchingGroup && ( )} } > } > {t("attributes-group:annotationsText")} {annotationsField.fields .filter( ( annotation ): annotation is Partial< ArrayField > => annotation.type === AnnotationType.String ) .map((item, index) => ( ))} ); }