Re-use User Profile logic from shared context (#1951)
This commit is contained in:
parent
7dfa1a8b19
commit
d85ff6e9be
4 changed files with 121 additions and 111 deletions
|
@ -1,4 +1,3 @@
|
||||||
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
|
||||||
import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
|
@ -12,17 +11,10 @@ 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 type { OnSaveCallback } from "./UserProfileTab";
|
import { useUserProfile } from "./UserProfileContext";
|
||||||
|
|
||||||
type AttributesGroupTabProps = {
|
export const AttributesGroupTab = () => {
|
||||||
config?: UserProfileConfig;
|
const { config, save } = useUserProfile();
|
||||||
onSave: OnSaveCallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const AttributesGroupTab = ({
|
|
||||||
config,
|
|
||||||
onSave,
|
|
||||||
}: AttributesGroupTabProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
|
@ -50,7 +42,7 @@ export const AttributesGroupTab = ({
|
||||||
(group) => group !== groupToDelete
|
(group) => group !== groupToDelete
|
||||||
);
|
);
|
||||||
|
|
||||||
onSave(
|
save(
|
||||||
{ ...config, groups },
|
{ ...config, groups },
|
||||||
{
|
{
|
||||||
successMessageKey: "attributes-group:deleteSuccess",
|
successMessageKey: "attributes-group:deleteSuccess",
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
|
||||||
import { CodeEditor, Language } from "@patternfly/react-code-editor";
|
import { CodeEditor, Language } from "@patternfly/react-code-editor";
|
||||||
import { ActionGroup, Button, Form, PageSection } from "@patternfly/react-core";
|
import { ActionGroup, Button, Form, PageSection } from "@patternfly/react-core";
|
||||||
import type { editor } from "monaco-editor";
|
import type { editor } from "monaco-editor";
|
||||||
|
@ -6,19 +5,10 @@ import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAlerts } from "../../components/alert/Alerts";
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
import { prettyPrintJSON } from "../../util";
|
import { prettyPrintJSON } from "../../util";
|
||||||
import type { OnSaveCallback } from "./UserProfileTab";
|
import { useUserProfile } from "./UserProfileContext";
|
||||||
|
|
||||||
type JsonEditorTabProps = {
|
export const JsonEditorTab = () => {
|
||||||
config?: UserProfileConfig;
|
const { config, save, isSaving } = useUserProfile();
|
||||||
onSave: OnSaveCallback;
|
|
||||||
isSaving: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const JsonEditorTab = ({
|
|
||||||
config,
|
|
||||||
onSave,
|
|
||||||
isSaving,
|
|
||||||
}: JsonEditorTabProps) => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { addError } = useAlerts();
|
const { addError } = useAlerts();
|
||||||
const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>();
|
const [editor, setEditor] = useState<editor.IStandaloneCodeEditor>();
|
||||||
|
@ -29,7 +19,7 @@ export const JsonEditorTab = ({
|
||||||
editor?.setValue(config ? prettyPrintJSON(config) : "");
|
editor?.setValue(config ? prettyPrintJSON(config) : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
function handleSave() {
|
||||||
const value = editor?.getValue();
|
const value = editor?.getValue();
|
||||||
|
|
||||||
if (!value) {
|
if (!value) {
|
||||||
|
@ -37,7 +27,7 @@ export const JsonEditorTab = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
onSave(JSON.parse(value));
|
save(JSON.parse(value));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
addError("realm-settings:invalidJsonError", error);
|
addError("realm-settings:invalidJsonError", error);
|
||||||
return;
|
return;
|
||||||
|
@ -54,7 +44,7 @@ export const JsonEditorTab = ({
|
||||||
/>
|
/>
|
||||||
<Form>
|
<Form>
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<Button variant="primary" onClick={save} isDisabled={isSaving}>
|
<Button variant="primary" onClick={handleSave} isDisabled={isSaving}>
|
||||||
{t("common:save")}
|
{t("common:save")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="link" onClick={resetCode} isDisabled={isSaving}>
|
<Button variant="link" onClick={resetCode} isDisabled={isSaving}>
|
||||||
|
|
76
src/realm-settings/user-profile/UserProfileContext.tsx
Normal file
76
src/realm-settings/user-profile/UserProfileContext.tsx
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
||||||
|
import { AlertVariant } from "@patternfly/react-core";
|
||||||
|
import React, { createContext, FunctionComponent, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
|
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
||||||
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
|
import useRequiredContext from "../../utils/useRequiredContext";
|
||||||
|
|
||||||
|
type UserProfileProps = {
|
||||||
|
config: UserProfileConfig | null;
|
||||||
|
save: SaveCallback;
|
||||||
|
isSaving: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SaveCallback = (
|
||||||
|
updatedConfig: UserProfileConfig,
|
||||||
|
options?: SaveOptions
|
||||||
|
) => Promise<void>;
|
||||||
|
|
||||||
|
export type SaveOptions = {
|
||||||
|
successMessageKey?: string;
|
||||||
|
errorMessageKey?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const UserProfile = createContext<UserProfileProps | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
export const UserProfileProvider: FunctionComponent = ({ children }) => {
|
||||||
|
const adminClient = useAdminClient();
|
||||||
|
const { realm } = useRealm();
|
||||||
|
const { addAlert, addError } = useAlerts();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [config, setConfig] = useState<UserProfileConfig | null>(null);
|
||||||
|
const [refreshCount, setRefreshCount] = useState(0);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
|
useFetch(
|
||||||
|
() => adminClient.users.getProfile({ realm }),
|
||||||
|
(config) => setConfig(config),
|
||||||
|
[refreshCount]
|
||||||
|
);
|
||||||
|
|
||||||
|
const save: SaveCallback = async (updatedConfig, options) => {
|
||||||
|
setIsSaving(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await adminClient.users.updateProfile({
|
||||||
|
...updatedConfig,
|
||||||
|
realm,
|
||||||
|
});
|
||||||
|
|
||||||
|
setRefreshCount(refreshCount + 1);
|
||||||
|
addAlert(
|
||||||
|
t(options?.successMessageKey ?? "realm-settings:userProfileSuccess"),
|
||||||
|
AlertVariant.success
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
addError(
|
||||||
|
options?.errorMessageKey ?? "realm-settings:userProfileError",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSaving(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<UserProfile.Provider value={{ config, save, isSaving }}>
|
||||||
|
{children}
|
||||||
|
</UserProfile.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useUserProfile = () => useRequiredContext(UserProfile);
|
|
@ -1,103 +1,55 @@
|
||||||
import type UserProfileConfig from "@keycloak/keycloak-admin-client/lib/defs/userProfileConfig";
|
import { Tab, TabTitleText } from "@patternfly/react-core";
|
||||||
import { AlertVariant, Tab, TabTitleText } from "@patternfly/react-core";
|
import React from "react";
|
||||||
import React, { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { useAlerts } from "../../components/alert/Alerts";
|
|
||||||
import {
|
import {
|
||||||
routableTab,
|
routableTab,
|
||||||
RoutableTabs,
|
RoutableTabs,
|
||||||
} from "../../components/routable-tabs/RoutableTabs";
|
} from "../../components/routable-tabs/RoutableTabs";
|
||||||
import { useAdminClient, useFetch } from "../../context/auth/AdminClient";
|
|
||||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
import { toUserProfile } from "../routes/UserProfile";
|
import { toUserProfile } from "../routes/UserProfile";
|
||||||
import { AttributesGroupTab } from "./AttributesGroupTab";
|
import { AttributesGroupTab } from "./AttributesGroupTab";
|
||||||
import { JsonEditorTab } from "./JsonEditorTab";
|
import { JsonEditorTab } from "./JsonEditorTab";
|
||||||
|
import { UserProfileProvider } from "./UserProfileContext";
|
||||||
export type OnSaveCallback = (
|
|
||||||
updatedProfiles: UserProfileConfig,
|
|
||||||
options?: OnSaveOptions
|
|
||||||
) => Promise<void>;
|
|
||||||
|
|
||||||
export type OnSaveOptions = {
|
|
||||||
successMessageKey?: string;
|
|
||||||
errorMessageKey?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UserProfileTab = () => {
|
export const UserProfileTab = () => {
|
||||||
const adminClient = useAdminClient();
|
|
||||||
const { realm } = useRealm();
|
const { realm } = useRealm();
|
||||||
const { t } = useTranslation("realm-settings");
|
const { t } = useTranslation("realm-settings");
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const { addAlert, addError } = useAlerts();
|
|
||||||
const [config, setConfig] = useState<UserProfileConfig>();
|
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
|
||||||
const [refreshCount, setRefreshCount] = useState(0);
|
|
||||||
|
|
||||||
useFetch(
|
|
||||||
() => adminClient.users.getProfile({ realm }),
|
|
||||||
(config) => setConfig(config),
|
|
||||||
[refreshCount]
|
|
||||||
);
|
|
||||||
|
|
||||||
const onSave: OnSaveCallback = async (
|
|
||||||
updatedProfiles: UserProfileConfig,
|
|
||||||
options?: OnSaveOptions
|
|
||||||
) => {
|
|
||||||
setIsSaving(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await adminClient.users.updateProfile({
|
|
||||||
...updatedProfiles,
|
|
||||||
realm,
|
|
||||||
});
|
|
||||||
|
|
||||||
setRefreshCount(refreshCount + 1);
|
|
||||||
addAlert(
|
|
||||||
t(options?.successMessageKey ?? "userProfileSuccess"),
|
|
||||||
AlertVariant.success
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
addError(
|
|
||||||
options?.errorMessageKey ?? "realm-settings:userProfileError",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSaving(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RoutableTabs
|
<UserProfileProvider>
|
||||||
defaultLocation={toUserProfile({ realm, tab: "attributes" })}
|
<RoutableTabs
|
||||||
mountOnEnter
|
defaultLocation={toUserProfile({ realm, tab: "attributes" })}
|
||||||
>
|
mountOnEnter
|
||||||
<Tab
|
|
||||||
title={<TabTitleText>{t("attributes")}</TabTitleText>}
|
|
||||||
{...routableTab({
|
|
||||||
to: toUserProfile({ realm, tab: "attributes" }),
|
|
||||||
history,
|
|
||||||
})}
|
|
||||||
></Tab>
|
|
||||||
<Tab
|
|
||||||
title={<TabTitleText>{t("attributesGroup")}</TabTitleText>}
|
|
||||||
data-testid="attributesGroupTab"
|
|
||||||
{...routableTab({
|
|
||||||
to: toUserProfile({ realm, tab: "attributesGroup" }),
|
|
||||||
history,
|
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<AttributesGroupTab config={config} onSave={onSave} />
|
<Tab
|
||||||
</Tab>
|
title={<TabTitleText>{t("attributes")}</TabTitleText>}
|
||||||
<Tab
|
{...routableTab({
|
||||||
title={<TabTitleText>{t("jsonEditor")}</TabTitleText>}
|
to: toUserProfile({ realm, tab: "attributes" }),
|
||||||
{...routableTab({
|
history,
|
||||||
to: toUserProfile({ realm, tab: "jsonEditor" }),
|
})}
|
||||||
history,
|
></Tab>
|
||||||
})}
|
<Tab
|
||||||
>
|
title={<TabTitleText>{t("attributesGroup")}</TabTitleText>}
|
||||||
<JsonEditorTab config={config} onSave={onSave} isSaving={isSaving} />
|
data-testid="attributesGroupTab"
|
||||||
</Tab>
|
{...routableTab({
|
||||||
</RoutableTabs>
|
to: toUserProfile({ realm, tab: "attributesGroup" }),
|
||||||
|
history,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<AttributesGroupTab />
|
||||||
|
</Tab>
|
||||||
|
<Tab
|
||||||
|
title={<TabTitleText>{t("jsonEditor")}</TabTitleText>}
|
||||||
|
{...routableTab({
|
||||||
|
to: toUserProfile({ realm, tab: "jsonEditor" }),
|
||||||
|
history,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<JsonEditorTab />
|
||||||
|
</Tab>
|
||||||
|
</RoutableTabs>
|
||||||
|
</UserProfileProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue