keycloak-scim/js/apps/admin-ui/src/realm-settings/user-profile/attribute/AddTranslationsDialog.tsx
Erik Jan de Wit 659f0f583f
changed name and added version number (#28157)
Signed-off-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
2024-04-19 14:10:34 -04:00

320 lines
10 KiB
TypeScript

import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
import {
Button,
Flex,
FlexItem,
Form,
FormGroup,
Label,
Modal,
ModalVariant,
Text,
TextContent,
TextVariants,
} from "@patternfly/react-core";
import { Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
import { Table } from "@patternfly/react-table/deprecated";
import { SearchIcon } from "@patternfly/react-icons";
import { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { FormProvider, useForm, useWatch } from "react-hook-form";
import { useRealm } from "../../../context/realm-context/RealmContext";
import { useWhoAmI } from "../../../context/whoami/WhoAmI";
import { adminClient } from "../../../admin-client";
import { PaginatingTableToolbar } from "../../../components/table-toolbar/PaginatingTableToolbar";
import { ListEmptyState } from "../../../components/list-empty-state/ListEmptyState";
import { useFetch } from "../../../utils/useFetch";
import { localeToDisplayName } from "../../../util";
import { DEFAULT_LOCALE } from "../../../i18n/i18n";
import { TextControl } from "@keycloak/keycloak-ui-shared";
type TranslationForm = {
locale: string;
value: string;
};
type Translations = {
key: string;
translations: TranslationForm[];
};
export type AddTranslationsDialogProps = {
translationKey: string;
onCancel: () => void;
toggleDialog: () => void;
onTranslationsAdded: (translations: Translations) => void;
};
export const AddTranslationsDialog = ({
translationKey,
onCancel,
toggleDialog,
onTranslationsAdded,
}: AddTranslationsDialogProps) => {
const { t } = useTranslation();
const { realm: realmName } = useRealm();
const [realm, setRealm] = useState<RealmRepresentation>();
const { whoAmI } = useWhoAmI();
const [max, setMax] = useState(10);
const [first, setFirst] = useState(0);
const [filter, setFilter] = useState("");
const form = useForm<{
key: string;
translations: TranslationForm[];
}>({
mode: "onChange",
});
const {
getValues,
handleSubmit,
setValue,
formState: { isValid },
} = form;
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 filteredLocales = useMemo(() => {
return combinedLocales.filter((locale) =>
localeToDisplayName(locale, whoAmI.getLocale())!
.toLowerCase()
.includes(filter.toLowerCase()),
);
}, [combinedLocales, filter, whoAmI]);
useEffect(() => {
combinedLocales.forEach((locale, rowIndex) => {
setValue(`translations.${rowIndex}`, {
locale,
value: "",
});
setValue("key", translationKey);
});
}, [combinedLocales, translationKey, setValue]);
const handleOk = () => {
const formData = getValues();
const updatedTranslations = formData.translations.map((translation) => {
if (translation.locale === filter) {
return {
...translation,
value:
formData.translations.find((t) => t.locale === filter)?.value ?? "",
};
}
return translation;
});
onTranslationsAdded({
key: formData.key,
translations: updatedTranslations,
});
toggleDialog();
};
const isRequiredTranslation = useWatch({
control: form.control,
name: "translations.0.value",
});
return (
<Modal
variant={ModalVariant.medium}
title={t("addTranslationsModalTitle")}
isOpen
onClose={toggleDialog}
actions={[
<Button
key="ok"
data-testid="okTranslationBtn"
variant="primary"
type="submit"
form="add-translation"
isDisabled={!isValid || !isRequiredTranslation}
>
{t("addTranslationDialogOkBtn")}
</Button>,
<Button
key="cancel"
data-testid="cancelTranslationBtn"
variant="link"
onClick={onCancel}
>
{t("cancel")}
</Button>,
]}
>
<Flex
direction={{ default: "column" }}
spaceItems={{ default: "spaceItemsNone" }}
>
<FlexItem>
<TextContent>
<Text component={TextVariants.p}>
{t("addTranslationsModalSubTitle")}{" "}
<strong>{t("addTranslationsModalSubTitleBolded")}</strong>
</Text>
</TextContent>
</FlexItem>
<FlexItem>
<FormProvider {...form}>
<Form
id="add-translation"
data-testid="addTranslationForm"
onSubmit={handleSubmit(handleOk)}
>
<TextControl
name="key"
label={t("translationKey")}
className="pf-v5-u-mt-md"
data-testid="translation-key"
isDisabled
/>
<FlexItem>
<TextContent>
<Text
className="pf-v5-u-font-size-sm pf-v5-u-font-weight-bold"
component={TextVariants.p}
>
{t("translationsTableHeading")}
</Text>
</TextContent>
<PaginatingTableToolbar
count={combinedLocales.length}
first={first}
max={max}
onNextClick={setFirst}
onPreviousClick={setFirst}
onPerPageSelect={(first, max) => {
setFirst(first);
setMax(max);
}}
inputGroupName={"search"}
inputGroupOnEnter={(search) => {
setFilter(search);
setFirst(0);
setMax(10);
}}
inputGroupPlaceholder={t("searchForLanguage")}
>
{filteredLocales.length === 0 && !filter && (
<ListEmptyState
hasIcon
message={t("noLanguages")}
instructions={t("noLanguagesInstructions")}
/>
)}
{filteredLocales.length === 0 && filter && (
<ListEmptyState
hasIcon
icon={SearchIcon}
isSearchVariant
message={t("noSearchResults")}
instructions={t("noLanguagesSearchResultsInstructions")}
/>
)}
{filteredLocales.length !== 0 && (
<Table
aria-label={t("addTranslationsDialogRowsTable")}
data-testid="add-translations-dialog-rows-table"
>
<Thead>
<Tr>
<Th className="pf-v5-u-py-lg">
{t("supportedLanguagesTableColumnName")}
</Th>
<Th className="pf-v5-u-py-lg">
{t("translationTableColumnName")}
</Th>
<Th aria-hidden="true" />
</Tr>
</Thead>
<Tbody>
{filteredLocales.map((locale, index) => {
const rowIndex = combinedLocales.findIndex(
(combinedLocale) => combinedLocale === locale,
);
return (
<Tr key={index}>
<Td
className="pf-m-sm pf-v5-u-px-sm"
dataLabel={t("supportedLanguage")}
>
<FormGroup fieldId="kc-supportedLanguage">
{localeToDisplayName(
locale,
whoAmI.getLocale(),
)}
{locale === defaultLocales.toString() && (
<Label
className="pf-v5-u-ml-xs"
color="blue"
>
{t("defaultLanguage")}
</Label>
)}
</FormGroup>
</Td>
<Td>
{locale === defaultLocales.toString() && (
<TextControl
name={`translations.${rowIndex}.value`}
label={t("translationValue")}
data-testid={`translation-value-${rowIndex}`}
rules={{
required: {
value: true,
message: t("required"),
},
}}
/>
)}
{locale !== defaultLocales.toString() && (
<TextControl
name={`translations.${rowIndex}.value`}
label={t("translationValue")}
data-testid={`translation-value-${rowIndex}`}
/>
)}
</Td>
</Tr>
);
})}
</Tbody>
</Table>
)}
</PaginatingTableToolbar>
</FlexItem>
</Form>
</FormProvider>
</FlexItem>
</Flex>
</Modal>
);
};