Added localization for User Profile attribute groups (#29374)
* resolved conflicts Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * added localization feature to up attributes groups Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * refactor Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * fix linting Signed-off-by: Jon Koops <jonkoops@gmail.com> * fixed attribute groups test Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * fixed another failing test Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> * reverted the test change Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> --------- Signed-off-by: Agnieszka Gancarczyk <agancarc@redhat.com> Signed-off-by: Jon Koops <jonkoops@gmail.com> Co-authored-by: Agnieszka Gancarczyk <agancarc@redhat.com> Co-authored-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
e200ccfa53
commit
b01e47feec
12 changed files with 674 additions and 135 deletions
|
@ -571,7 +571,9 @@ describe("Clients test", () => {
|
|||
cy.findByTestId("importClient").click();
|
||||
cy.findByTestId("realm-file").selectFile(
|
||||
"cypress/fixtures/partial-import-test-data/import-identical-client.json",
|
||||
{ action: "drag-drop" },
|
||||
{
|
||||
action: "drag-drop",
|
||||
},
|
||||
);
|
||||
|
||||
cy.wait(1000);
|
||||
|
|
|
@ -60,9 +60,10 @@ export default class ListingPage extends CommonElements {
|
|||
#tableNameColumnPrefix = "name-column-";
|
||||
#rowGroup = "table:visible tbody[role='rowgroup']";
|
||||
#tableHeaderCheckboxItemAllRows = "input[aria-label='Select all rows']";
|
||||
|
||||
#searchBtnInModal =
|
||||
".pf-v5-c-modal-box .pf-v5-c-toolbar__content-section button.pf-m-control:visible";
|
||||
#menuContent = ".pf-v5-c-menu__content";
|
||||
#menuItemText = ".pf-v5-c-menu__item-text";
|
||||
|
||||
#getSearchInput() {
|
||||
return cy.findAllByTestId("table-search-input").last().find("input");
|
||||
|
@ -189,6 +190,14 @@ export default class ListingPage extends CommonElements {
|
|||
return this;
|
||||
}
|
||||
|
||||
clickMenuDelete() {
|
||||
cy.get(this.#menuContent)
|
||||
.find(this.#menuItemText)
|
||||
.contains("Delete")
|
||||
.click({ force: true });
|
||||
return this;
|
||||
}
|
||||
|
||||
clickItemCheckbox(itemName: string) {
|
||||
cy.get(this.#itemsRows)
|
||||
.contains(itemName)
|
||||
|
@ -247,7 +256,7 @@ export default class ListingPage extends CommonElements {
|
|||
|
||||
deleteItem(itemName: string) {
|
||||
this.clickRowDetails(itemName);
|
||||
this.clickDetailMenu("Delete");
|
||||
this.clickMenuDelete();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -3085,9 +3085,13 @@ sendClientIdOnLogout=Send 'client_id' in logout requests
|
|||
sendClientIdOnLogoutHelp=If the 'client_id' parameter should be sent in logout requests.
|
||||
addAttributeTranslationBtn=Add translation button
|
||||
addAttributeTranslationInfo=Add translations for this field using the icon next to the "Display name" field.
|
||||
addAttributeDisplayNameTranslation=Add translation for the display name
|
||||
addAttributeDisplayDescriptionTranslation=Add translation for the display description
|
||||
addTranslationsModalTitle=Add translations
|
||||
addTranslationsModalSubTitle=You are able to translate the "Display name" based on your locale or preferred languages. In addition, you are also able to create or edit the "Display name" translations in the
|
||||
addTranslationsModalSubTitleBolded=Realm settings > Localization > Realm overrides.
|
||||
addTranslationsModalSubTitleDescription=You are able to translate the "Display description" based on your locale or preferred languages. In addition, you are also able to create or edit the "Display description" translations in the
|
||||
addAttributesGroupTranslationInfo=Add translations for this field using the icon next to the "Display name" field.
|
||||
translationKey=Key
|
||||
translationsTableHeading=Translations
|
||||
searchForLanguage=Search for language
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
PageSection,
|
||||
} from "@patternfly/react-core";
|
||||
import { flatten } from "flat";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { FormProvider, useForm, useFormContext } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
|
@ -29,8 +29,7 @@ import { AttributeAnnotations } from "./user-profile/attribute/AttributeAnnotati
|
|||
import { AttributeGeneralSettings } from "./user-profile/attribute/AttributeGeneralSettings";
|
||||
import { AttributePermission } from "./user-profile/attribute/AttributePermission";
|
||||
import { AttributeValidations } from "./user-profile/attribute/AttributeValidations";
|
||||
import { DEFAULT_LOCALE } from "../i18n/i18n";
|
||||
|
||||
import useLocale from "../utils/useLocale";
|
||||
import "./realm-settings-section.css";
|
||||
|
||||
type TranslationForm = {
|
||||
|
@ -157,10 +156,10 @@ const CreateAttributeFormContent = ({
|
|||
|
||||
export default function NewAttributeSettings() {
|
||||
const { adminClient } = useAdminClient();
|
||||
|
||||
const { realm: realmName, attributeName } = useParams<AttributeParams>();
|
||||
const form = useForm<UserProfileAttributeFormFields>();
|
||||
const { t } = useTranslation();
|
||||
const combinedLocales = useLocale();
|
||||
const navigate = useNavigate();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const [config, setConfig] = useState<UserProfileConfig | null>(null);
|
||||
|
@ -172,20 +171,6 @@ export default function NewAttributeSettings() {
|
|||
const [generatedDisplayName, setGeneratedDisplayName] = useState<string>("");
|
||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||
|
||||
const defaultSupportedLocales = useMemo(() => {
|
||||
return realm?.supportedLocales?.length
|
||||
? realm.supportedLocales
|
||||
: [DEFAULT_LOCALE];
|
||||
}, [realm]);
|
||||
|
||||
const defaultLocales = useMemo(() => {
|
||||
return realm?.defaultLocale?.length ? [realm.defaultLocale] : [];
|
||||
}, [realm]);
|
||||
|
||||
const combinedLocales = useMemo(() => {
|
||||
return Array.from(new Set([...defaultLocales, ...defaultSupportedLocales]));
|
||||
}, [defaultLocales, defaultSupportedLocales]);
|
||||
|
||||
useFetch(
|
||||
() => adminClient.realms.findOne({ realm: realmName }),
|
||||
(realm) => {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { fetchWithError } from "@keycloak/keycloak-admin-client";
|
||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import { UserProfileConfig } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||
import { AdminEnvironment, useEnvironment } from "@keycloak/keycloak-ui-shared";
|
||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import {
|
||||
AlertVariant,
|
||||
ButtonVariant,
|
||||
|
@ -14,11 +14,10 @@ import {
|
|||
DropdownItem,
|
||||
DropdownSeparator,
|
||||
} from "@patternfly/react-core/deprecated";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAdminClient } from "../admin-client";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import type { KeyValueType } from "../components/key-value-form/key-value-convert";
|
||||
|
@ -28,17 +27,16 @@ import {
|
|||
} from "../components/routable-tabs/RoutableTabs";
|
||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||
import { useRealms } from "../context/RealmsContext";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { toDashboard } from "../dashboard/routes/Dashboard";
|
||||
import helpUrls from "../help-urls";
|
||||
import { DEFAULT_LOCALE } from "../i18n/i18n";
|
||||
import { convertFormValuesToObject, convertToFormValues } from "../util";
|
||||
import { getAuthorizationHeaders } from "../utils/getAuthorizationHeaders";
|
||||
import { joinPath } from "../utils/joinPath";
|
||||
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
|
||||
import { RealmSettingsEmailTab } from "./EmailTab";
|
||||
import { RealmSettingsGeneralTab } from "./GeneralTab";
|
||||
import { LocalizationTab } from "./localization/LocalizationTab";
|
||||
import { RealmSettingsLoginTab } from "./LoginTab";
|
||||
import { PartialExportDialog } from "./PartialExport";
|
||||
import { PartialImportDialog } from "./PartialImport";
|
||||
|
@ -50,11 +48,13 @@ import { RealmSettingsTokensTab } from "./TokensTab";
|
|||
import { UserRegistration } from "./UserRegistration";
|
||||
import { EventsTab } from "./event-config/EventsTab";
|
||||
import { KeysTab } from "./keys/KeysTab";
|
||||
import { LocalizationTab } from "./localization/LocalizationTab";
|
||||
import { ClientPoliciesTab, toClientPolicies } from "./routes/ClientPolicies";
|
||||
import { RealmSettingsTab, toRealmSettings } from "./routes/RealmSettings";
|
||||
import { SecurityDefenses } from "./security-defences/SecurityDefenses";
|
||||
import { UserProfileTab } from "./user-profile/UserProfileTab";
|
||||
import useLocale from "../utils/useLocale";
|
||||
import { useAdminClient } from "../admin-client";
|
||||
import { useAccess } from "../context/access/Access";
|
||||
|
||||
export interface UIRealmRepresentation extends RealmRepresentation {
|
||||
upConfig?: UserProfileConfig;
|
||||
|
@ -77,14 +77,12 @@ const RealmSettingsHeader = ({
|
|||
}: RealmSettingsHeaderProps) => {
|
||||
const { adminClient } = useAdminClient();
|
||||
const { environment } = useEnvironment<AdminEnvironment>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { refresh: refreshRealms } = useRealms();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const navigate = useNavigate();
|
||||
const [partialImportOpen, setPartialImportOpen] = useState(false);
|
||||
const [partialExportOpen, setPartialExportOpen] = useState(false);
|
||||
|
||||
const { hasAccess } = useAccess();
|
||||
const canManageRealm = hasAccess("manage-realm");
|
||||
|
||||
|
@ -187,22 +185,20 @@ export const RealmSettingsTabs = ({
|
|||
refresh,
|
||||
}: RealmSettingsTabsProps) => {
|
||||
const { adminClient } = useAdminClient();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
const { realm: realmName } = useRealm();
|
||||
const { refresh: refreshRealms } = useRealms();
|
||||
const combinedLocales = useLocale();
|
||||
const navigate = useNavigate();
|
||||
const isFeatureEnabled = useIsFeatureEnabled();
|
||||
const [tableData, setTableData] = useState<
|
||||
Record<string, string>[] | undefined
|
||||
>(undefined);
|
||||
|
||||
const { control, setValue, getValues } = useForm({
|
||||
mode: "onChange",
|
||||
});
|
||||
const [key, setKey] = useState(0);
|
||||
|
||||
const refreshHeader = () => {
|
||||
setKey(key + 1);
|
||||
};
|
||||
|
@ -211,20 +207,6 @@ export const RealmSettingsTabs = ({
|
|||
convertToFormValues(r, setValue);
|
||||
};
|
||||
|
||||
const defaultSupportedLocales = useMemo(() => {
|
||||
return realm.supportedLocales?.length
|
||||
? realm.supportedLocales
|
||||
: [DEFAULT_LOCALE];
|
||||
}, [realm]);
|
||||
|
||||
const defaultLocales = useMemo(() => {
|
||||
return realm.defaultLocale?.length ? [realm.defaultLocale] : [];
|
||||
}, [realm]);
|
||||
|
||||
const combinedLocales = useMemo(() => {
|
||||
return Array.from(new Set([...defaultLocales, ...defaultSupportedLocales]));
|
||||
}, [defaultLocales, defaultSupportedLocales]);
|
||||
|
||||
useEffect(() => {
|
||||
setupForm();
|
||||
const fetchLocalizationTexts = async () => {
|
||||
|
|
|
@ -1,18 +1,28 @@
|
|||
import type { UserProfileGroup } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import {
|
||||
ActionGroup,
|
||||
Alert,
|
||||
Button,
|
||||
FormGroup,
|
||||
Grid,
|
||||
GridItem,
|
||||
PageSection,
|
||||
Text,
|
||||
TextContent,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
FormProvider,
|
||||
SubmitHandler,
|
||||
useForm,
|
||||
useWatch,
|
||||
} from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||
import { TextControl } from "@keycloak/keycloak-ui-shared";
|
||||
|
||||
import { HelpItem, TextControl } from "@keycloak/keycloak-ui-shared";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { FormAccess } from "../../components/form/FormAccess";
|
||||
import { KeyValueInput } from "../../components/key-value-form/KeyValueInput";
|
||||
import type { KeyValueType } from "../../components/key-value-form/key-value-convert";
|
||||
|
@ -21,8 +31,16 @@ import { useRealm } from "../../context/realm-context/RealmContext";
|
|||
import type { EditAttributesGroupParams } from "../routes/EditAttributesGroup";
|
||||
import { toUserProfile } from "../routes/UserProfile";
|
||||
import { useUserProfile } from "./UserProfileContext";
|
||||
|
||||
import { useFetch } from "../../utils/useFetch";
|
||||
import { GlobeRouteIcon } from "@patternfly/react-icons";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
import useLocale from "../../utils/useLocale";
|
||||
import {
|
||||
AddTranslationsDialog,
|
||||
TranslationsType,
|
||||
} from "./attribute/AddTranslationsDialog";
|
||||
import "../realm-settings-section.css";
|
||||
import { useAdminClient } from "../../admin-client";
|
||||
|
||||
function parseAnnotations(input: Record<string, unknown>): KeyValueType[] {
|
||||
return Object.entries(input).reduce((p, [key, value]) => {
|
||||
|
@ -46,6 +64,21 @@ type FormFields = Required<Omit<UserProfileGroup, "annotations">> & {
|
|||
annotations: KeyValueType[];
|
||||
};
|
||||
|
||||
type TranslationForm = {
|
||||
locale: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
type Translations = {
|
||||
key: string;
|
||||
translations: TranslationForm[];
|
||||
};
|
||||
|
||||
type TranslationsSets = {
|
||||
displayHeader: Translations;
|
||||
displayDescription: Translations;
|
||||
};
|
||||
|
||||
const defaultValues: FormFields = {
|
||||
annotations: [],
|
||||
displayDescription: "",
|
||||
|
@ -54,12 +87,39 @@ const defaultValues: FormFields = {
|
|||
};
|
||||
|
||||
export default function AttributesGroupForm() {
|
||||
const { adminClient } = useAdminClient();
|
||||
const { t } = useTranslation();
|
||||
const { realm } = useRealm();
|
||||
const { realm: realmName } = useRealm();
|
||||
const { config, save } = useUserProfile();
|
||||
const navigate = useNavigate();
|
||||
const combinedLocales = useLocale();
|
||||
const params = useParams<EditAttributesGroupParams>();
|
||||
const form = useForm<FormFields>({ defaultValues });
|
||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||
const { addError } = useAlerts();
|
||||
const editMode = params.name ? true : false;
|
||||
const [newAttributesGroupName, setNewAttributesGroupName] = useState("");
|
||||
const [
|
||||
generatedAttributesGroupDisplayName,
|
||||
setGeneratedAttributesGroupDisplayName,
|
||||
] = useState("");
|
||||
const [
|
||||
generatedAttributesGroupDisplayDescription,
|
||||
setGeneratedAttributesGroupDisplayDescription,
|
||||
] = useState("");
|
||||
const [addTranslationsModalOpen, toggleModal] = useToggle();
|
||||
const regexPattern = /\$\{([^}]+)\}/;
|
||||
const [type, setType] = useState<TranslationsType>();
|
||||
const [translationsData, setTranslationsData] = useState<TranslationsSets>({
|
||||
displayHeader: {
|
||||
key: "",
|
||||
translations: [],
|
||||
},
|
||||
displayDescription: {
|
||||
key: "",
|
||||
translations: [],
|
||||
},
|
||||
});
|
||||
|
||||
const matchingGroup = useMemo(
|
||||
() => config?.groups?.find(({ name }) => name === params.name),
|
||||
|
@ -78,6 +138,185 @@ export default function AttributesGroupForm() {
|
|||
form.reset({ ...defaultValues, ...matchingGroup, annotations });
|
||||
}, [matchingGroup]);
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue(
|
||||
"displayHeader",
|
||||
matchingGroup
|
||||
? matchingGroup.displayHeader!
|
||||
: generatedAttributesGroupDisplayName,
|
||||
);
|
||||
form.setValue(
|
||||
"displayDescription",
|
||||
matchingGroup
|
||||
? matchingGroup.displayDescription!
|
||||
: generatedAttributesGroupDisplayDescription,
|
||||
);
|
||||
}, [
|
||||
generatedAttributesGroupDisplayName,
|
||||
generatedAttributesGroupDisplayDescription,
|
||||
]);
|
||||
|
||||
useFetch(
|
||||
() => adminClient.realms.findOne({ realm: realmName }),
|
||||
(realm) => {
|
||||
if (!realm) {
|
||||
throw new Error(t("notFound"));
|
||||
}
|
||||
setRealm(realm);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useFetch(
|
||||
async () => {
|
||||
const translationsToSaveDisplayHeader: Translations[] = [];
|
||||
const translationsToSaveDisplayDescription: Translations[] = [];
|
||||
const formData = form.getValues();
|
||||
|
||||
const translationsResults = await Promise.all(
|
||||
combinedLocales.map(async (selectedLocale) => {
|
||||
try {
|
||||
const translations =
|
||||
await adminClient.realms.getRealmLocalizationTexts({
|
||||
realm: realmName,
|
||||
selectedLocale,
|
||||
});
|
||||
|
||||
const formattedDisplayHeaderKey = formData.displayHeader?.substring(
|
||||
2,
|
||||
formData.displayHeader.length - 1,
|
||||
);
|
||||
const formattedDisplayDescriptionKey =
|
||||
formData.displayDescription?.substring(
|
||||
2,
|
||||
formData.displayDescription.length - 1,
|
||||
);
|
||||
|
||||
return {
|
||||
locale: selectedLocale,
|
||||
headerTranslation: translations[formattedDisplayHeaderKey] ?? "",
|
||||
descriptionTranslation:
|
||||
translations[formattedDisplayDescriptionKey] ?? "",
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error fetching translations for ${selectedLocale}:`,
|
||||
error,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
translationsResults.forEach((translationsResult) => {
|
||||
if (translationsResult) {
|
||||
const { locale, headerTranslation, descriptionTranslation } =
|
||||
translationsResult;
|
||||
translationsToSaveDisplayHeader.push({
|
||||
key: formData.displayHeader?.substring(
|
||||
2,
|
||||
formData.displayHeader.length - 1,
|
||||
),
|
||||
translations: [
|
||||
{
|
||||
locale,
|
||||
value: headerTranslation,
|
||||
},
|
||||
],
|
||||
});
|
||||
translationsToSaveDisplayDescription.push({
|
||||
key: formData.displayDescription?.substring(
|
||||
2,
|
||||
formData.displayDescription.length - 1,
|
||||
),
|
||||
translations: [
|
||||
{
|
||||
locale,
|
||||
value: descriptionTranslation,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
translationsToSaveDisplayHeader,
|
||||
translationsToSaveDisplayDescription,
|
||||
};
|
||||
},
|
||||
(data) => {
|
||||
setTranslationsData({
|
||||
displayHeader: {
|
||||
key: data.translationsToSaveDisplayHeader[0].key,
|
||||
translations: data.translationsToSaveDisplayHeader.flatMap(
|
||||
(translationData) => translationData.translations,
|
||||
),
|
||||
},
|
||||
displayDescription: {
|
||||
key: data.translationsToSaveDisplayDescription[0].key,
|
||||
translations: data.translationsToSaveDisplayDescription.flatMap(
|
||||
(translationData) => translationData.translations,
|
||||
),
|
||||
},
|
||||
});
|
||||
},
|
||||
[combinedLocales],
|
||||
);
|
||||
|
||||
const saveTranslations = async () => {
|
||||
const addLocalization = async (
|
||||
key: string,
|
||||
locale: string,
|
||||
value: string,
|
||||
) => {
|
||||
try {
|
||||
await adminClient.realms.addLocalization(
|
||||
{
|
||||
realm: realmName,
|
||||
selectedLocale: locale,
|
||||
key: key,
|
||||
},
|
||||
value,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error saving translation for locale ${locale}: ${error}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
if (
|
||||
translationsData.displayHeader &&
|
||||
translationsData.displayHeader.translations.length > 0
|
||||
) {
|
||||
for (const translation of translationsData.displayHeader.translations) {
|
||||
await addLocalization(
|
||||
translationsData.displayHeader.key,
|
||||
translation.locale,
|
||||
translation.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
translationsData.displayDescription &&
|
||||
translationsData.displayDescription.translations.length > 0
|
||||
) {
|
||||
for (const translation of translationsData.displayDescription
|
||||
.translations) {
|
||||
await addLocalization(
|
||||
translationsData.displayDescription.key,
|
||||
translation.locale,
|
||||
translation.value,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error while processing translations: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
const onSubmit: SubmitHandler<FormFields> = async (values) => {
|
||||
if (!config) {
|
||||
return;
|
||||
|
@ -96,15 +335,133 @@ export default function AttributesGroupForm() {
|
|||
groups[updateAt] = updatedGroup;
|
||||
}
|
||||
|
||||
if (realm?.internationalizationEnabled) {
|
||||
const hasNonEmptyDisplayHeaderTranslations =
|
||||
translationsData.displayHeader.translations.some(
|
||||
(translation) => translation.value.trim() !== "",
|
||||
);
|
||||
|
||||
const hasNonEmptyDisplayDescriptionTranslations =
|
||||
translationsData.displayDescription.translations.some(
|
||||
(translation) => translation.value.trim() !== "",
|
||||
);
|
||||
|
||||
if (
|
||||
!hasNonEmptyDisplayHeaderTranslations ||
|
||||
!hasNonEmptyDisplayDescriptionTranslations
|
||||
) {
|
||||
addError("createAttributeError", t("translationError"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const success = await save({ ...config, groups });
|
||||
|
||||
if (success) {
|
||||
navigate(toUserProfile({ realm, tab: "attributes-group" }));
|
||||
await saveTranslations();
|
||||
navigate(toUserProfile({ realm: realmName, tab: "attributes-group" }));
|
||||
}
|
||||
};
|
||||
|
||||
const attributesGroupDisplayName = useWatch({
|
||||
control: form.control,
|
||||
name: "displayHeader",
|
||||
});
|
||||
|
||||
const attributesGroupDisplayDescription = useWatch({
|
||||
control: form.control,
|
||||
name: "displayDescription",
|
||||
});
|
||||
|
||||
const handleAttributesGroupNameChange = (
|
||||
event: React.FormEvent<HTMLInputElement>,
|
||||
value: string,
|
||||
) => {
|
||||
const newDisplayName =
|
||||
value !== "" && realm?.internationalizationEnabled
|
||||
? "${profile.attribute-group." + `${value}}`
|
||||
: "";
|
||||
const newDisplayDescription =
|
||||
value !== "" && realm?.internationalizationEnabled
|
||||
? "${profile.attribute-group-description." + `${value}}`
|
||||
: "";
|
||||
setNewAttributesGroupName(value);
|
||||
setGeneratedAttributesGroupDisplayName(newDisplayName);
|
||||
setGeneratedAttributesGroupDisplayDescription(newDisplayDescription);
|
||||
};
|
||||
|
||||
const attributesGroupDisplayPatternMatch = regexPattern.test(
|
||||
attributesGroupDisplayName || attributesGroupDisplayDescription,
|
||||
);
|
||||
|
||||
const formattedAttributesGroupDisplayName =
|
||||
attributesGroupDisplayName?.substring(
|
||||
2,
|
||||
attributesGroupDisplayName.length - 1,
|
||||
);
|
||||
const formattedAttributesGroupDisplayDescription =
|
||||
attributesGroupDisplayDescription?.substring(
|
||||
2,
|
||||
attributesGroupDisplayDescription.length - 1,
|
||||
);
|
||||
|
||||
const handleHeaderTranslationsAdded = (headerTranslations: Translations) => {
|
||||
setTranslationsData((prev) => ({
|
||||
...prev,
|
||||
displayHeader: headerTranslations,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleDescriptionTranslationsAdded = (
|
||||
descriptionTranslations: Translations,
|
||||
) => {
|
||||
setTranslationsData((prev) => ({
|
||||
...prev,
|
||||
displayDescription: descriptionTranslations,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleToggleDialog = () => {
|
||||
toggleModal();
|
||||
};
|
||||
|
||||
const groupDisplayNameKey =
|
||||
type === "displayHeader"
|
||||
? formattedAttributesGroupDisplayName
|
||||
: `profile.attribute-group.${newAttributesGroupName}`;
|
||||
const groupDisplayDescriptionKey =
|
||||
type === "displayDescription"
|
||||
? formattedAttributesGroupDisplayDescription
|
||||
: `profile.attribute-group-description.${newAttributesGroupName}`;
|
||||
|
||||
return (
|
||||
<>
|
||||
{addTranslationsModalOpen && (
|
||||
<AddTranslationsDialog
|
||||
translationKey={
|
||||
type === "displayHeader"
|
||||
? groupDisplayNameKey
|
||||
: groupDisplayDescriptionKey
|
||||
}
|
||||
type={
|
||||
type === "displayHeader" ? "displayHeader" : "displayDescription"
|
||||
}
|
||||
translations={
|
||||
type === "displayHeader"
|
||||
? translationsData.displayHeader
|
||||
: translationsData.displayDescription
|
||||
}
|
||||
onTranslationsAdded={
|
||||
type === "displayHeader"
|
||||
? handleHeaderTranslationsAdded
|
||||
: handleDescriptionTranslationsAdded
|
||||
}
|
||||
toggleDialog={handleToggleDialog}
|
||||
onCancel={() => {
|
||||
toggleModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ViewHeader
|
||||
titleKey={matchingGroup ? "editGroupText" : "createGroupText"}
|
||||
divider
|
||||
|
@ -116,22 +473,136 @@ export default function AttributesGroupForm() {
|
|||
name="name"
|
||||
label={t("nameField")}
|
||||
labelIcon={t("nameHintHelp")}
|
||||
rules={{ required: t("required") }}
|
||||
readOnly={!!matchingGroup}
|
||||
isDisabled={!!matchingGroup || editMode}
|
||||
rules={{
|
||||
required: {
|
||||
value: true,
|
||||
message: t("required"),
|
||||
},
|
||||
onChange: (event) => {
|
||||
handleAttributesGroupNameChange(event, event.target.value);
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{!!matchingGroup && (
|
||||
<input type="hidden" {...form.register("name")} />
|
||||
)}
|
||||
<TextControl
|
||||
name="displayHeader"
|
||||
<FormGroup
|
||||
label={t("displayHeaderField")}
|
||||
labelIcon={t("displayHeaderHintHelp")}
|
||||
/>
|
||||
<TextControl
|
||||
name="displayDescription"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("displayHeaderHintHelp")}
|
||||
fieldLabelId="displayHeaderField"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-attributes-group-display-header"
|
||||
>
|
||||
<Grid hasGutter>
|
||||
<GridItem span={realm?.internationalizationEnabled ? 11 : 12}>
|
||||
<TextInput
|
||||
id="kc-attributes-group-display-header"
|
||||
data-testid="attributes-group-display-header"
|
||||
isDisabled={
|
||||
(realm?.internationalizationEnabled &&
|
||||
newAttributesGroupName !== "") ||
|
||||
(editMode && attributesGroupDisplayPatternMatch)
|
||||
}
|
||||
value={
|
||||
editMode
|
||||
? attributesGroupDisplayName
|
||||
: realm?.internationalizationEnabled
|
||||
? generatedAttributesGroupDisplayName
|
||||
: undefined
|
||||
}
|
||||
{...form.register("displayHeader")}
|
||||
/>
|
||||
{generatedAttributesGroupDisplayName && (
|
||||
<Alert
|
||||
className="pf-v5-u-mt-sm"
|
||||
variant="info"
|
||||
isInline
|
||||
isPlain
|
||||
title={t("addAttributesGroupTranslationInfo")}
|
||||
/>
|
||||
)}
|
||||
</GridItem>
|
||||
{realm?.internationalizationEnabled && (
|
||||
<GridItem span={1}>
|
||||
<Button
|
||||
variant="link"
|
||||
className="pf-m-plain"
|
||||
data-testid="addAttributeDisplayNameTranslationBtn"
|
||||
aria-label={t("addAttributeDisplayNameTranslation")}
|
||||
isDisabled={!newAttributesGroupName && !editMode}
|
||||
onClick={() => {
|
||||
setType("displayHeader");
|
||||
toggleModal();
|
||||
}}
|
||||
icon={<GlobeRouteIcon />}
|
||||
/>
|
||||
</GridItem>
|
||||
)}
|
||||
</Grid>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("displayDescriptionField")}
|
||||
labelIcon={t("displayDescriptionHintHelp")}
|
||||
/>
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("displayDescriptionHintHelp")}
|
||||
fieldLabelId="displayDescriptionField"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-attributes-group-display-description"
|
||||
>
|
||||
<Grid hasGutter>
|
||||
<GridItem span={realm?.internationalizationEnabled ? 11 : 12}>
|
||||
<TextInput
|
||||
id="kc-attributes-group-display-description"
|
||||
data-testid="attributes-group-display-description"
|
||||
isDisabled={
|
||||
(realm?.internationalizationEnabled &&
|
||||
newAttributesGroupName !== "") ||
|
||||
(editMode && attributesGroupDisplayPatternMatch)
|
||||
}
|
||||
value={
|
||||
editMode
|
||||
? attributesGroupDisplayDescription
|
||||
: realm?.internationalizationEnabled
|
||||
? generatedAttributesGroupDisplayDescription
|
||||
: undefined
|
||||
}
|
||||
{...form.register("displayDescription")}
|
||||
/>
|
||||
{generatedAttributesGroupDisplayDescription && (
|
||||
<Alert
|
||||
className="pf-v5-u-mt-sm"
|
||||
variant="info"
|
||||
isInline
|
||||
isPlain
|
||||
title={t("addAttributesGroupTranslationInfo")}
|
||||
/>
|
||||
)}
|
||||
</GridItem>
|
||||
{realm?.internationalizationEnabled && (
|
||||
<GridItem span={1}>
|
||||
<Button
|
||||
variant="link"
|
||||
className="pf-m-plain"
|
||||
data-testid="addAttributeDisplayDescriptionTranslationBtn"
|
||||
aria-label={t(
|
||||
"addAttributeDisplayDescriptionTranslation",
|
||||
)}
|
||||
isDisabled={!newAttributesGroupName && !editMode}
|
||||
onClick={() => {
|
||||
setType("displayDescription");
|
||||
toggleModal();
|
||||
}}
|
||||
icon={<GlobeRouteIcon />}
|
||||
/>
|
||||
</GridItem>
|
||||
)}
|
||||
</Grid>
|
||||
</FormGroup>
|
||||
<TextContent>
|
||||
<Text component="h2">{t("annotationsText")}</Text>
|
||||
</TextContent>
|
||||
|
@ -151,7 +622,10 @@ export default function AttributesGroupForm() {
|
|||
component={(props) => (
|
||||
<Link
|
||||
{...props}
|
||||
to={toUserProfile({ realm, tab: "attributes-group" })}
|
||||
to={toUserProfile({
|
||||
realm: realmName,
|
||||
tab: "attributes-group",
|
||||
})}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
|
|
|
@ -18,10 +18,22 @@ import { useRealm } from "../../context/realm-context/RealmContext";
|
|||
import { toEditAttributesGroup } from "../routes/EditAttributesGroup";
|
||||
import { toNewAttributesGroup } from "../routes/NewAttributesGroup";
|
||||
import { useUserProfile } from "./UserProfileContext";
|
||||
import useLocale from "../../utils/useLocale";
|
||||
import { useAdminClient } from "../../admin-client";
|
||||
|
||||
export const AttributesGroupTab = () => {
|
||||
type AttributesGroupTabProps = {
|
||||
setTableData: React.Dispatch<
|
||||
React.SetStateAction<Record<string, string>[] | undefined>
|
||||
>;
|
||||
};
|
||||
|
||||
export const AttributesGroupTab = ({
|
||||
setTableData,
|
||||
}: AttributesGroupTabProps) => {
|
||||
const { adminClient } = useAdminClient();
|
||||
const { config, save } = useUserProfile();
|
||||
const { t } = useTranslation();
|
||||
const combinedLocales = useLocale();
|
||||
const navigate = useNavigate();
|
||||
const { realm } = useRealm();
|
||||
const [key, setKey] = useState(0);
|
||||
|
@ -44,18 +56,69 @@ export const AttributesGroupTab = () => {
|
|||
),
|
||||
continueButtonLabel: "delete",
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm() {
|
||||
onConfirm: async () => {
|
||||
const groups = (config?.groups ?? []).filter(
|
||||
(group) => group !== groupToDelete,
|
||||
);
|
||||
const translationsForDisplayHeaderToDelete =
|
||||
groupToDelete?.displayHeader?.substring(
|
||||
2,
|
||||
groupToDelete?.displayHeader.length - 1,
|
||||
);
|
||||
const translationsForDisplayDescriptionToDelete =
|
||||
groupToDelete?.displayDescription?.substring(
|
||||
2,
|
||||
groupToDelete?.displayDescription.length - 1,
|
||||
);
|
||||
|
||||
save(
|
||||
{ ...config, groups },
|
||||
{
|
||||
successMessageKey: "deleteSuccess",
|
||||
errorMessageKey: "deleteAttributeGroupError",
|
||||
},
|
||||
);
|
||||
try {
|
||||
await Promise.all(
|
||||
combinedLocales.map(async (locale) => {
|
||||
try {
|
||||
const response =
|
||||
await adminClient.realms.getRealmLocalizationTexts({
|
||||
realm,
|
||||
selectedLocale: locale,
|
||||
});
|
||||
|
||||
if (response) {
|
||||
await adminClient.realms.deleteRealmLocalizationTexts({
|
||||
realm,
|
||||
selectedLocale: locale,
|
||||
key: translationsForDisplayHeaderToDelete,
|
||||
});
|
||||
|
||||
await adminClient.realms.deleteRealmLocalizationTexts({
|
||||
realm,
|
||||
selectedLocale: locale,
|
||||
key: translationsForDisplayDescriptionToDelete,
|
||||
});
|
||||
|
||||
const updatedData =
|
||||
await adminClient.realms.getRealmLocalizationTexts({
|
||||
realm,
|
||||
selectedLocale: locale,
|
||||
});
|
||||
setTableData([updatedData]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error removing translations for ${locale}`);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
save(
|
||||
{ ...config, groups },
|
||||
{
|
||||
successMessageKey: "deleteSuccess",
|
||||
errorMessageKey: "deleteAttributeGroupError",
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error removing translations or updating attributes group: ${error}`,
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -91,7 +154,12 @@ export const AttributesGroupTab = () => {
|
|||
name: "name",
|
||||
displayKey: "columnName",
|
||||
cellRenderer: (group) => (
|
||||
<Link to={toEditAttributesGroup({ realm, name: group.name! })}>
|
||||
<Link
|
||||
to={toEditAttributesGroup({
|
||||
realm,
|
||||
name: group.name!,
|
||||
})}
|
||||
>
|
||||
{group.name}
|
||||
</Link>
|
||||
),
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import type { UserProfileAttribute } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||
import {
|
||||
Button,
|
||||
|
@ -15,20 +14,19 @@ import {
|
|||
} from "@patternfly/react-core/deprecated";
|
||||
import { FilterIcon } from "@patternfly/react-icons";
|
||||
import { uniqBy } from "lodash-es";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { useAdminClient } from "../../admin-client";
|
||||
import { DraggableTable } from "../../authentication/components/DraggableTable";
|
||||
import { useConfirmDialog } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { KeycloakSpinner } from "../../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { DEFAULT_LOCALE } from "../../i18n/i18n";
|
||||
import { useFetch } from "../../utils/useFetch";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
import { toAddAttribute } from "../routes/AddAttribute";
|
||||
import { toAttribute } from "../routes/Attribute";
|
||||
import { useUserProfile } from "./UserProfileContext";
|
||||
import useLocale from "../../utils/useLocale";
|
||||
import { useAdminClient } from "../../admin-client";
|
||||
|
||||
const RESTRICTED_ATTRIBUTES = ["username", "email"];
|
||||
|
||||
|
@ -42,42 +40,16 @@ type AttributesTabProps = {
|
|||
|
||||
export const AttributesTab = ({ setTableData }: AttributesTabProps) => {
|
||||
const { adminClient } = useAdminClient();
|
||||
|
||||
const { config, save } = useUserProfile();
|
||||
const { realm: realmName } = useRealm();
|
||||
const { realm } = useRealm();
|
||||
const { t } = useTranslation();
|
||||
const combinedLocales = useLocale();
|
||||
const navigate = useNavigate();
|
||||
const [filter, setFilter] = useState("allGroups");
|
||||
const [isFilterTypeDropdownOpen, toggleIsFilterTypeDropdownOpen] =
|
||||
useToggle();
|
||||
const [data, setData] = useState(config?.attributes);
|
||||
const [attributeToDelete, setAttributeToDelete] = useState("");
|
||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||
|
||||
useFetch(
|
||||
() => adminClient.realms.findOne({ realm: realmName }),
|
||||
(realm) => {
|
||||
if (!realm) {
|
||||
throw new Error(t("notFound"));
|
||||
}
|
||||
setRealm(realm);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const defaultSupportedLocales = useMemo(() => {
|
||||
return realm?.supportedLocales?.length
|
||||
? realm.supportedLocales
|
||||
: [DEFAULT_LOCALE];
|
||||
}, [realm]);
|
||||
|
||||
const defaultLocales = useMemo(() => {
|
||||
return realm?.defaultLocale?.length ? [realm.defaultLocale] : [];
|
||||
}, [realm]);
|
||||
|
||||
const combinedLocales = useMemo(() => {
|
||||
return Array.from(new Set([...defaultLocales, ...defaultSupportedLocales]));
|
||||
}, [defaultLocales, defaultSupportedLocales]);
|
||||
|
||||
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||
titleKey: t("deleteAttributeConfirmTitle"),
|
||||
|
@ -105,20 +77,20 @@ export const AttributesTab = ({ setTableData }: AttributesTabProps) => {
|
|||
try {
|
||||
const response =
|
||||
await adminClient.realms.getRealmLocalizationTexts({
|
||||
realm: realmName,
|
||||
realm,
|
||||
selectedLocale: locale,
|
||||
});
|
||||
|
||||
if (response) {
|
||||
await adminClient.realms.deleteRealmLocalizationTexts({
|
||||
realm: realmName,
|
||||
realm,
|
||||
selectedLocale: locale,
|
||||
key: formattedTranslationsToDelete,
|
||||
});
|
||||
|
||||
const updatedData =
|
||||
await adminClient.realms.getRealmLocalizationTexts({
|
||||
realm: realmName,
|
||||
realm,
|
||||
selectedLocale: locale,
|
||||
});
|
||||
setTableData([updatedData]);
|
||||
|
@ -182,7 +154,7 @@ export const AttributesTab = ({ setTableData }: AttributesTabProps) => {
|
|||
const cellFormatter = (row: UserProfileAttribute) => (
|
||||
<Link
|
||||
to={toAttribute({
|
||||
realm: realmName,
|
||||
realm,
|
||||
attributeName: row.name!,
|
||||
})}
|
||||
key={row.name}
|
||||
|
@ -241,7 +213,7 @@ export const AttributesTab = ({ setTableData }: AttributesTabProps) => {
|
|||
data-testid="createAttributeBtn"
|
||||
variant="primary"
|
||||
component={(props) => (
|
||||
<Link {...props} to={toAddAttribute({ realm: realmName })} />
|
||||
<Link {...props} to={toAddAttribute({ realm })} />
|
||||
)}
|
||||
>
|
||||
{t("createAttribute")}
|
||||
|
@ -268,7 +240,7 @@ export const AttributesTab = ({ setTableData }: AttributesTabProps) => {
|
|||
onClick: (_key, _idx, component) => {
|
||||
navigate(
|
||||
toAttribute({
|
||||
realm: realmName,
|
||||
realm,
|
||||
attributeName: component.name,
|
||||
}),
|
||||
);
|
||||
|
|
|
@ -50,7 +50,7 @@ export const UserProfileTab = ({ setTableData }: UserProfileTabProps) => {
|
|||
data-testid="attributesGroupTab"
|
||||
{...attributesGroupTab}
|
||||
>
|
||||
<AttributesGroupTab />
|
||||
<AttributesGroupTab setTableData={setTableData} />
|
||||
</Tab>
|
||||
<Tab
|
||||
title={<TabTitleText>{t("jsonEditor")}</TabTitleText>}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import { TextControl } from "@keycloak/keycloak-ui-shared";
|
||||
import {
|
||||
Button,
|
||||
Flex,
|
||||
|
@ -13,19 +12,25 @@ import {
|
|||
TextContent,
|
||||
TextVariants,
|
||||
} from "@patternfly/react-core";
|
||||
import { SearchIcon } from "@patternfly/react-icons";
|
||||
import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
|
||||
import { SearchIcon } from "@patternfly/react-icons";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { FormProvider, useForm, useWatch } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAdminClient } from "../../../admin-client";
|
||||
import { ListEmptyState } from "../../../components/list-empty-state/ListEmptyState";
|
||||
import { PaginatingTableToolbar } from "../../../components/table-toolbar/PaginatingTableToolbar";
|
||||
import { FormProvider, useForm, useWatch } from "react-hook-form";
|
||||
import { useRealm } from "../../../context/realm-context/RealmContext";
|
||||
import { useWhoAmI } from "../../../context/whoami/WhoAmI";
|
||||
import { DEFAULT_LOCALE } from "../../../i18n/i18n";
|
||||
import { localeToDisplayName } from "../../../util";
|
||||
import { useFetch } from "../../../utils/useFetch";
|
||||
import { localeToDisplayName } from "../../../util";
|
||||
import useLocale from "../../../utils/useLocale";
|
||||
import { TextControl } from "@keycloak/keycloak-ui-shared";
|
||||
|
||||
export type TranslationsType =
|
||||
| "displayName"
|
||||
| "displayHeader"
|
||||
| "displayDescription";
|
||||
|
||||
type TranslationForm = {
|
||||
locale: string;
|
||||
|
@ -40,6 +45,7 @@ type Translations = {
|
|||
export type AddTranslationsDialogProps = {
|
||||
translationKey: string;
|
||||
translations: Translations;
|
||||
type: TranslationsType;
|
||||
onCancel: () => void;
|
||||
toggleDialog: () => void;
|
||||
onTranslationsAdded: (translations: Translations) => void;
|
||||
|
@ -48,14 +54,15 @@ export type AddTranslationsDialogProps = {
|
|||
export const AddTranslationsDialog = ({
|
||||
translationKey,
|
||||
translations,
|
||||
type,
|
||||
onCancel,
|
||||
toggleDialog,
|
||||
onTranslationsAdded,
|
||||
}: AddTranslationsDialogProps) => {
|
||||
const { adminClient } = useAdminClient();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { realm: realmName } = useRealm();
|
||||
const combinedLocales = useLocale();
|
||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
const [max, setMax] = useState(10);
|
||||
|
@ -90,20 +97,10 @@ export const AddTranslationsDialog = ({
|
|||
[],
|
||||
);
|
||||
|
||||
const defaultSupportedLocales = useMemo(() => {
|
||||
return realm?.supportedLocales!.length
|
||||
? realm.supportedLocales
|
||||
: [DEFAULT_LOCALE];
|
||||
}, [realm]);
|
||||
|
||||
const defaultLocales = useMemo(() => {
|
||||
return realm?.defaultLocale!.length ? [realm.defaultLocale] : [];
|
||||
}, [realm]);
|
||||
|
||||
const combinedLocales = useMemo(() => {
|
||||
return Array.from(new Set([...defaultLocales, ...defaultSupportedLocales]));
|
||||
}, [defaultLocales, defaultSupportedLocales]);
|
||||
|
||||
const filteredLocales = useMemo(() => {
|
||||
return combinedLocales.filter((locale) =>
|
||||
localeToDisplayName(locale, whoAmI.getLocale())!
|
||||
|
@ -233,7 +230,9 @@ export const AddTranslationsDialog = ({
|
|||
<FlexItem>
|
||||
<TextContent>
|
||||
<Text component={TextVariants.p}>
|
||||
{t("addTranslationsModalSubTitle")}{" "}
|
||||
{type !== "displayHeader"
|
||||
? t("addTranslationsModalSubTitleDescription")
|
||||
: t("addTranslationsModalSubTitle")}{" "}
|
||||
<strong>{t("addTranslationsModalSubTitleBolded")}</strong>
|
||||
</Text>
|
||||
</TextContent>
|
||||
|
|
|
@ -23,7 +23,6 @@ import { useEffect, useState } from "react";
|
|||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormErrorText, HelpItem } from "@keycloak/keycloak-ui-shared";
|
||||
import { useAdminClient } from "../../../admin-client";
|
||||
import { FormAccess } from "../../../components/form/FormAccess";
|
||||
import { KeycloakSpinner } from "../../../components/keycloak-spinner/KeycloakSpinner";
|
||||
import { useRealm } from "../../../context/realm-context/RealmContext";
|
||||
|
@ -32,8 +31,11 @@ import { useParams } from "../../../utils/useParams";
|
|||
import useToggle from "../../../utils/useToggle";
|
||||
import { USERNAME_EMAIL } from "../../NewAttributeSettings";
|
||||
import { AttributeParams } from "../../routes/Attribute";
|
||||
import { AddTranslationsDialog } from "./AddTranslationsDialog";
|
||||
|
||||
import {
|
||||
AddTranslationsDialog,
|
||||
TranslationsType,
|
||||
} from "./AddTranslationsDialog";
|
||||
import { useAdminClient } from "../../../admin-client";
|
||||
import "../../realm-settings-section.css";
|
||||
|
||||
const REQUIRED_FOR = [
|
||||
|
@ -62,7 +64,6 @@ export const AttributeGeneralSettings = ({
|
|||
onHandlingGeneratedDisplayName,
|
||||
}: AttributeGeneralSettingsProps) => {
|
||||
const { adminClient } = useAdminClient();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { realm: realmName } = useRealm();
|
||||
const form = useFormContext();
|
||||
|
@ -79,6 +80,7 @@ export const AttributeGeneralSettings = ({
|
|||
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||
const [newAttributeName, setNewAttributeName] = useState("");
|
||||
const [generatedDisplayName, setGeneratedDisplayName] = useState("");
|
||||
const [type, setType] = useState<TranslationsType>();
|
||||
const [translationsData, setTranslationsData] = useState<Translations>({
|
||||
key: "",
|
||||
translations: [],
|
||||
|
@ -184,6 +186,7 @@ export const AttributeGeneralSettings = ({
|
|||
: `profile.attributes.${newAttributeName}`
|
||||
}
|
||||
translations={translationsData}
|
||||
type={type ?? "displayName"}
|
||||
onTranslationsAdded={handleTranslationsAdded}
|
||||
toggleDialog={handleToggleDialog}
|
||||
onCancel={() => {
|
||||
|
@ -265,6 +268,7 @@ export const AttributeGeneralSettings = ({
|
|||
aria-label={t("addAttributeTranslationBtn")}
|
||||
isDisabled={!newAttributeName && !editMode}
|
||||
onClick={() => {
|
||||
setType("displayName");
|
||||
toggleModal();
|
||||
}}
|
||||
icon={<GlobeRouteIcon />}
|
||||
|
|
40
js/apps/admin-ui/src/utils/useLocale.ts
Normal file
40
js/apps/admin-ui/src/utils/useLocale.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import { useMemo, useState } from "react";
|
||||
import { DEFAULT_LOCALE } from "../i18n/i18n";
|
||||
import { useFetch } from "./useFetch";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useAdminClient } from "../admin-client";
|
||||
|
||||
export default function useLocale() {
|
||||
const { adminClient } = useAdminClient();
|
||||
const { t } = useTranslation();
|
||||
const { realm: realmName } = useRealm();
|
||||
const [realm, setRealm] = useState<RealmRepresentation>();
|
||||
|
||||
useFetch(
|
||||
() => adminClient.realms.findOne({ realm: realmName }),
|
||||
(realm) => {
|
||||
if (!realm) {
|
||||
throw new Error(t("notFound"));
|
||||
}
|
||||
setRealm(realm);
|
||||
},
|
||||
[],
|
||||
);
|
||||
const defaultSupportedLocales = useMemo(() => {
|
||||
return realm?.supportedLocales?.length
|
||||
? realm.supportedLocales
|
||||
: [DEFAULT_LOCALE];
|
||||
}, [realm]);
|
||||
|
||||
const defaultLocales = useMemo(() => {
|
||||
return realm?.defaultLocale?.length ? [realm.defaultLocale] : [];
|
||||
}, [realm]);
|
||||
|
||||
const combinedLocales = useMemo(() => {
|
||||
return Array.from(new Set([...defaultLocales, ...defaultSupportedLocales]));
|
||||
}, [defaultLocales, defaultSupportedLocales]);
|
||||
|
||||
return combinedLocales;
|
||||
}
|
Loading…
Reference in a new issue