Add form for User Profile attribute groups (#2021)
* Add form for User Profile attribute groups * Add breadcrumbs to create and edit pages * Add hints to form fields * Link directly to details from overview * Disable name field when editing * Disable adding annotations when not valid
This commit is contained in:
parent
baf2380d1e
commit
0152e5868d
9 changed files with 425 additions and 16 deletions
|
@ -820,7 +820,8 @@ export default {
|
||||||
exportFail: "Could not export realm: '{{error}}'",
|
exportFail: "Could not export realm: '{{error}}'",
|
||||||
},
|
},
|
||||||
"attributes-group": {
|
"attributes-group": {
|
||||||
createButtonText: "Create attributes group",
|
createGroupText: "Create attributes group",
|
||||||
|
editGroupText: "Edit attributes group",
|
||||||
tableTitle: "Attributes groups",
|
tableTitle: "Attributes groups",
|
||||||
columnName: "Name",
|
columnName: "Name",
|
||||||
columnDisplayName: "Display name",
|
columnDisplayName: "Display name",
|
||||||
|
@ -833,5 +834,21 @@ export default {
|
||||||
"Are you sure you want to permanently delete the attributes group <1>{{group}}</1>?",
|
"Are you sure you want to permanently delete the attributes group <1>{{group}}</1>?",
|
||||||
deleteSuccess: "Attributes group deleted.",
|
deleteSuccess: "Attributes group deleted.",
|
||||||
deleteError: "Could not delete user attributes group: {{error}}",
|
deleteError: "Could not delete user attributes group: {{error}}",
|
||||||
|
nameField: "Name",
|
||||||
|
nameHint:
|
||||||
|
"A unique name for the group. This name will be used to reference the group when binding an attribute to a group.",
|
||||||
|
displayHeaderField: "Display name",
|
||||||
|
displayHeaderHint:
|
||||||
|
"A user-friendly name for the group that should be used when rendering a group of attributes in user-facing forms. Supports keys for localized values as well. For example: ${profile.attribute.group.address}.",
|
||||||
|
displayDescriptionField: "Display description",
|
||||||
|
displayDescriptionHint:
|
||||||
|
"A text that should be used as a tooltip when rendering user-facing forms.",
|
||||||
|
annotationsText: "Annotations",
|
||||||
|
addAnnotationText: "Add annotation",
|
||||||
|
removeAnnotationText: "Remove annotation",
|
||||||
|
keyPlaceholder: "Type a key",
|
||||||
|
keyLabel: "Key",
|
||||||
|
valuePlaceholder: "Type a value",
|
||||||
|
valueLabel: "Value",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,8 @@ import { EditClientPolicyConditionRoute } from "./routes/EditCondition";
|
||||||
import { UserProfileRoute } from "./routes/UserProfile";
|
import { UserProfileRoute } from "./routes/UserProfile";
|
||||||
import { AddAttributeRoute } from "./routes/AddAttribute";
|
import { AddAttributeRoute } from "./routes/AddAttribute";
|
||||||
import { KeysRoute } from "./routes/KeysTab";
|
import { KeysRoute } from "./routes/KeysTab";
|
||||||
|
import { NewAttributesGroupRoute } from "./routes/NewAttributesGroup";
|
||||||
|
import { EditAttributesGroupRoute } from "./routes/EditAttributesGroup";
|
||||||
|
|
||||||
const routes: RouteDef[] = [
|
const routes: RouteDef[] = [
|
||||||
RealmSettingsRoute,
|
RealmSettingsRoute,
|
||||||
|
@ -29,6 +31,8 @@ const routes: RouteDef[] = [
|
||||||
EditClientPolicyConditionRoute,
|
EditClientPolicyConditionRoute,
|
||||||
UserProfileRoute,
|
UserProfileRoute,
|
||||||
AddAttributeRoute,
|
AddAttributeRoute,
|
||||||
|
NewAttributesGroupRoute,
|
||||||
|
EditAttributesGroupRoute,
|
||||||
];
|
];
|
||||||
|
|
||||||
export default routes;
|
export default routes;
|
||||||
|
|
22
src/realm-settings/routes/EditAttributesGroup.ts
Normal file
22
src/realm-settings/routes/EditAttributesGroup.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import type { LocationDescriptorObject } from "history";
|
||||||
|
import { lazy } from "react";
|
||||||
|
import { generatePath } from "react-router-dom";
|
||||||
|
import type { RouteDef } from "../../route-config";
|
||||||
|
|
||||||
|
export type EditAttributesGroupParams = {
|
||||||
|
realm: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EditAttributesGroupRoute: RouteDef = {
|
||||||
|
path: "/:realm/realm-settings/userProfile/attributesGroup/edit/:name",
|
||||||
|
component: lazy(() => import("../user-profile/AttributesGroupDetails")),
|
||||||
|
breadcrumb: (t) => t("attributes-group:editGroupText"),
|
||||||
|
access: "view-realm",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toEditAttributesGroup = (
|
||||||
|
params: EditAttributesGroupParams
|
||||||
|
): LocationDescriptorObject => ({
|
||||||
|
pathname: generatePath(EditAttributesGroupRoute.path, params),
|
||||||
|
});
|
21
src/realm-settings/routes/NewAttributesGroup.ts
Normal file
21
src/realm-settings/routes/NewAttributesGroup.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import type { LocationDescriptorObject } from "history";
|
||||||
|
import { lazy } from "react";
|
||||||
|
import { generatePath } from "react-router-dom";
|
||||||
|
import type { RouteDef } from "../../route-config";
|
||||||
|
|
||||||
|
export type NewAttributesGroupParams = {
|
||||||
|
realm: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const NewAttributesGroupRoute: RouteDef = {
|
||||||
|
path: "/:realm/realm-settings/userProfile/attributesGroup/new",
|
||||||
|
component: lazy(() => import("../user-profile/AttributesGroupDetails")),
|
||||||
|
breadcrumb: (t) => t("attributes-group:createGroupText"),
|
||||||
|
access: "view-realm",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const toNewAttributesGroup = (
|
||||||
|
params: NewAttributesGroupParams
|
||||||
|
): LocationDescriptorObject => ({
|
||||||
|
pathname: generatePath(NewAttributesGroupRoute.path, params),
|
||||||
|
});
|
|
@ -13,7 +13,7 @@ export type UserProfileParams = {
|
||||||
export const UserProfileRoute: RouteDef = {
|
export const UserProfileRoute: RouteDef = {
|
||||||
path: "/:realm/realm-settings/userProfile/:tab",
|
path: "/:realm/realm-settings/userProfile/:tab",
|
||||||
component: lazy(() => import("../RealmSettingsSection")),
|
component: lazy(() => import("../RealmSettingsSection")),
|
||||||
breadcrumb: (t) => t("realmSettings"),
|
breadcrumb: (t) => t("realm-settings:userProfile"),
|
||||||
access: "view-realm",
|
access: "view-realm",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
11
src/realm-settings/user-profile/AttributesGroupDetails.tsx
Normal file
11
src/realm-settings/user-profile/AttributesGroupDetails.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import React from "react";
|
||||||
|
import AttributesGroupForm from "./AttributesGroupForm";
|
||||||
|
import { UserProfileProvider } from "./UserProfileContext";
|
||||||
|
|
||||||
|
const AttributesGroupDetails = () => (
|
||||||
|
<UserProfileProvider>
|
||||||
|
<AttributesGroupForm />
|
||||||
|
</UserProfileProvider>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AttributesGroupDetails;
|
322
src/realm-settings/user-profile/AttributesGroupForm.tsx
Normal file
322
src/realm-settings/user-profile/AttributesGroupForm.tsx
Normal file
|
@ -0,0 +1,322 @@
|
||||||
|
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<string, unknown>) {
|
||||||
|
return Object.entries(input).map<Annotation>(([key, value]) => {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return { type: AnnotationType.String, key, value };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: AnnotationType.Unknown, key, value };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function transformAnnotations(input: Annotation[]): Record<string, unknown> {
|
||||||
|
return Object.fromEntries(
|
||||||
|
input
|
||||||
|
.filter((annotation) => annotation.key.length > 0)
|
||||||
|
.map((annotation) => [annotation.key, annotation.value] as const)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormFields = Required<Omit<UserProfileGroup, "annotations">> & {
|
||||||
|
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<Partial<EditAttributesGroupParams>>();
|
||||||
|
const form = useForm<FormFields>({ defaultValues, shouldUnregister: false });
|
||||||
|
const annotationsField = useFieldArray<Annotation>({
|
||||||
|
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<FormFields> = 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 (
|
||||||
|
<>
|
||||||
|
<ViewHeader
|
||||||
|
titleKey={
|
||||||
|
matchingGroup
|
||||||
|
? "attributes-group:editGroupText"
|
||||||
|
: "attributes-group:createGroupText"
|
||||||
|
}
|
||||||
|
divider
|
||||||
|
/>
|
||||||
|
<PageSection variant="light" onSubmit={form.handleSubmit(onSubmit)}>
|
||||||
|
<FormAccess isHorizontal role="manage-realm">
|
||||||
|
<FormGroup
|
||||||
|
label={t("attributes-group:nameField")}
|
||||||
|
fieldId="kc-name"
|
||||||
|
isRequired
|
||||||
|
helperTextInvalid={t("common:required")}
|
||||||
|
validated={form.errors.name ? "error" : "default"}
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="attributes-group:nameHint"
|
||||||
|
fieldLabelId="attributes-group:nameField"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
ref={form.register({ required: true })}
|
||||||
|
type="text"
|
||||||
|
id="kc-name"
|
||||||
|
name="name"
|
||||||
|
isDisabled={!!matchingGroup}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("attributes-group:displayHeaderField")}
|
||||||
|
fieldId="kc-display-header"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="attributes-group:displayHeaderHint"
|
||||||
|
fieldLabelId="attributes-group:displayHeaderField"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
ref={form.register()}
|
||||||
|
type="text"
|
||||||
|
id="kc-display-header"
|
||||||
|
name="displayHeader"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<FormGroup
|
||||||
|
label={t("attributes-group:displayDescriptionField")}
|
||||||
|
fieldId="kc-display-description"
|
||||||
|
labelIcon={
|
||||||
|
<HelpItem
|
||||||
|
helpText="attributes-group:displayDescriptionHint"
|
||||||
|
fieldLabelId="attributes-group:displayDescriptionField"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
ref={form.register()}
|
||||||
|
type="text"
|
||||||
|
id="kc-display-description"
|
||||||
|
name="displayDescription"
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<TextContent>
|
||||||
|
<Text component="h2">{t("attributes-group:annotationsText")}</Text>
|
||||||
|
</TextContent>
|
||||||
|
<FormGroup
|
||||||
|
label={t("attributes-group:annotationsText")}
|
||||||
|
fieldId="kc-annotations"
|
||||||
|
>
|
||||||
|
<Flex direction={{ default: "column" }}>
|
||||||
|
{annotationsField.fields
|
||||||
|
.filter(
|
||||||
|
(
|
||||||
|
annotation
|
||||||
|
): annotation is Partial<
|
||||||
|
ArrayField<StringAnnotation, "id">
|
||||||
|
> => annotation.type === AnnotationType.String
|
||||||
|
)
|
||||||
|
.map((item, index) => (
|
||||||
|
<Flex key={item.id}>
|
||||||
|
<FlexItem grow={{ default: "grow" }}>
|
||||||
|
<TextInput
|
||||||
|
name={`annotations[${index}].key`}
|
||||||
|
ref={form.register()}
|
||||||
|
placeholder={t("attributes-group:keyPlaceholder")}
|
||||||
|
aria-label={t("attributes-group:keyLabel")}
|
||||||
|
defaultValue={item.key}
|
||||||
|
/>
|
||||||
|
</FlexItem>
|
||||||
|
<FlexItem
|
||||||
|
grow={{ default: "grow" }}
|
||||||
|
spacer={{ default: "spacerNone" }}
|
||||||
|
>
|
||||||
|
<TextInput
|
||||||
|
name={`annotations[${index}].value`}
|
||||||
|
ref={form.register()}
|
||||||
|
placeholder={t("attributes-group:valuePlaceholder")}
|
||||||
|
aria-label={t("attributes-group:valueLabel")}
|
||||||
|
defaultValue={item.value}
|
||||||
|
/>
|
||||||
|
</FlexItem>
|
||||||
|
<FlexItem>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
title={t("attributes-group:removeAnnotationText")}
|
||||||
|
aria-label={t("attributes-group:removeAnnotationText")}
|
||||||
|
isDisabled={annotationsField.fields.length === 1}
|
||||||
|
onClick={() => removeAnnotation(index)}
|
||||||
|
>
|
||||||
|
<MinusCircleIcon />
|
||||||
|
</Button>
|
||||||
|
</FlexItem>
|
||||||
|
</Flex>
|
||||||
|
))}
|
||||||
|
</Flex>
|
||||||
|
<ActionList>
|
||||||
|
<ActionListItem>
|
||||||
|
<Button
|
||||||
|
className="pf-u-px-0 pf-u-mt-sm"
|
||||||
|
variant="link"
|
||||||
|
icon={<PlusCircleIcon />}
|
||||||
|
isDisabled={!annotationsValid}
|
||||||
|
onClick={addAnnotation}
|
||||||
|
>
|
||||||
|
{t("attributes-group:addAnnotationText")}
|
||||||
|
</Button>
|
||||||
|
</ActionListItem>
|
||||||
|
</ActionList>
|
||||||
|
</FormGroup>
|
||||||
|
<ActionGroup>
|
||||||
|
<Button variant="primary" type="submit">
|
||||||
|
{t("common:save")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
component={(props) => (
|
||||||
|
<Link
|
||||||
|
{...props}
|
||||||
|
to={toUserProfile({ realm, tab: "attributesGroup" })}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t("common:cancel")}
|
||||||
|
</Button>
|
||||||
|
</ActionGroup>
|
||||||
|
</FormAccess>
|
||||||
|
</PageSection>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
|
@ -11,12 +11,16 @@ import { Link, useHistory } from "react-router-dom";
|
||||||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||||
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
||||||
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||||
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
|
import { toEditAttributesGroup } from "../routes/EditAttributesGroup";
|
||||||
|
import { toNewAttributesGroup } from "../routes/NewAttributesGroup";
|
||||||
import { useUserProfile } from "./UserProfileContext";
|
import { useUserProfile } from "./UserProfileContext";
|
||||||
|
|
||||||
export const AttributesGroupTab = () => {
|
export const AttributesGroupTab = () => {
|
||||||
const { config, save } = useUserProfile();
|
const { config, save } = useUserProfile();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
const { realm } = useRealm();
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
const [groupToDelete, setGroupToDelete] = useState<UserProfileGroup>();
|
const [groupToDelete, setGroupToDelete] = useState<UserProfileGroup>();
|
||||||
|
|
||||||
|
@ -66,9 +70,12 @@ export const AttributesGroupTab = () => {
|
||||||
ariaLabelKey="attributes-group:tableTitle"
|
ariaLabelKey="attributes-group:tableTitle"
|
||||||
toolbarItem={
|
toolbarItem={
|
||||||
<ToolbarItem>
|
<ToolbarItem>
|
||||||
{/* TODO: Add link to page */}
|
<Button
|
||||||
<Button component={(props) => <Link {...props} to={{}} />}>
|
component={(props) => (
|
||||||
{t("attributes-group:createButtonText")}
|
<Link {...props} to={toNewAttributesGroup({ realm })} />
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t("attributes-group:createGroupText")}
|
||||||
</Button>
|
</Button>
|
||||||
</ToolbarItem>
|
</ToolbarItem>
|
||||||
}
|
}
|
||||||
|
@ -76,6 +83,11 @@ export const AttributesGroupTab = () => {
|
||||||
{
|
{
|
||||||
name: "name",
|
name: "name",
|
||||||
displayKey: "attributes-group:columnName",
|
displayKey: "attributes-group:columnName",
|
||||||
|
cellRenderer: (group) => (
|
||||||
|
<Link to={toEditAttributesGroup({ realm, name: group.name! })}>
|
||||||
|
{group.name}
|
||||||
|
</Link>
|
||||||
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "displayHeader",
|
name: "displayHeader",
|
||||||
|
@ -87,11 +99,6 @@ export const AttributesGroupTab = () => {
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
actions={[
|
actions={[
|
||||||
{
|
|
||||||
title: t("common:edit"),
|
|
||||||
// TODO: Add link to page.
|
|
||||||
onRowClick: () => history.push({}),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: t("common:delete"),
|
title: t("common:delete"),
|
||||||
onRowClick: deleteAttributeGroup,
|
onRowClick: deleteAttributeGroup,
|
||||||
|
@ -101,9 +108,10 @@ export const AttributesGroupTab = () => {
|
||||||
<ListEmptyState
|
<ListEmptyState
|
||||||
message={t("attributes-group:emptyStateMessage")}
|
message={t("attributes-group:emptyStateMessage")}
|
||||||
instructions={t("attributes-group:emptyStateInstructions")}
|
instructions={t("attributes-group:emptyStateInstructions")}
|
||||||
primaryActionText={t("attributes-group:createButtonText")}
|
primaryActionText={t("attributes-group:createGroupText")}
|
||||||
// TODO: Add link to page.
|
onPrimaryAction={() =>
|
||||||
onPrimaryAction={() => history.push({})}
|
history.push(toNewAttributesGroup({ realm }))
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -16,7 +16,7 @@ type UserProfileProps = {
|
||||||
export type SaveCallback = (
|
export type SaveCallback = (
|
||||||
updatedConfig: UserProfileConfig,
|
updatedConfig: UserProfileConfig,
|
||||||
options?: SaveOptions
|
options?: SaveOptions
|
||||||
) => Promise<void>;
|
) => Promise<boolean>;
|
||||||
|
|
||||||
export type SaveOptions = {
|
export type SaveOptions = {
|
||||||
successMessageKey?: string;
|
successMessageKey?: string;
|
||||||
|
@ -51,19 +51,23 @@ export const UserProfileProvider: FunctionComponent = ({ children }) => {
|
||||||
realm,
|
realm,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setIsSaving(false);
|
||||||
setRefreshCount(refreshCount + 1);
|
setRefreshCount(refreshCount + 1);
|
||||||
addAlert(
|
addAlert(
|
||||||
t(options?.successMessageKey ?? "realm-settings:userProfileSuccess"),
|
t(options?.successMessageKey ?? "realm-settings:userProfileSuccess"),
|
||||||
AlertVariant.success
|
AlertVariant.success
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
setIsSaving(false);
|
||||||
addError(
|
addError(
|
||||||
options?.errorMessageKey ?? "realm-settings:userProfileError",
|
options?.errorMessageKey ?? "realm-settings:userProfileError",
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
setIsSaving(false);
|
return false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
Loading…
Reference in a new issue