Unify utils for interpolated labels under ui-shared
(#25203)
Signed-off-by: Jon Koops <jonkoops@gmail.com>
This commit is contained in:
parent
3b26e5d489
commit
dc0455b73c
14 changed files with 61 additions and 98 deletions
|
@ -7,6 +7,7 @@ import {
|
|||
Spinner,
|
||||
} from "@patternfly/react-core";
|
||||
import { ExternalLinkSquareAltIcon } from "@patternfly/react-icons";
|
||||
import { TFunction } from "i18next";
|
||||
import { useKeycloak } from "keycloak-masthead";
|
||||
import { useState } from "react";
|
||||
import { ErrorOption, useForm } from "react-hook-form";
|
||||
|
@ -16,6 +17,7 @@ import {
|
|||
setUserProfileServerError,
|
||||
useAlerts,
|
||||
} from "ui-shared";
|
||||
|
||||
import {
|
||||
getPersonalInfo,
|
||||
getSupportedLocales,
|
||||
|
@ -65,7 +67,7 @@ const PersonalInfo = () => {
|
|||
{ responseData: { errors: error as any } },
|
||||
(name: string | number, error: unknown) =>
|
||||
setError(name as string, error as ErrorOption),
|
||||
(key: TFuncKey, param?: object) => t(key, { ...param }),
|
||||
((key: TFuncKey, param?: object) => t(key, param as any)) as TFunction,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
@ -87,7 +89,10 @@ const PersonalInfo = () => {
|
|||
form={form}
|
||||
userProfileMetadata={userProfileMetadata}
|
||||
supportedLocales={supportedLocales}
|
||||
t={(key: unknown, params) => t(key as TFuncKey, { ...params })}
|
||||
t={
|
||||
((key: unknown, params) =>
|
||||
t(key as TFuncKey, params as any)) as TFunction
|
||||
}
|
||||
renderer={(attribute) =>
|
||||
attribute.name === "email" &&
|
||||
updateEmailFeatureEnabled &&
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import { TFunction } from "i18next";
|
||||
import { TFuncKey } from "../i18n";
|
||||
import { UserProfileAttributeMetadata } from "../api/representations";
|
||||
|
||||
export const isBundleKey = (displayName?: string) =>
|
||||
displayName?.includes("${");
|
||||
export const unWrap = (key: string) => key.substring(2, key.length - 1);
|
||||
|
||||
export const label = (attribute: UserProfileAttributeMetadata, t: TFunction) =>
|
||||
(isBundleKey(attribute.displayName)
|
||||
? t(unWrap(attribute.displayName!) as TFuncKey)
|
||||
: attribute.displayName) || attribute.name;
|
||||
|
||||
const ROOT_ATTRIBUTES = ["username", "firstName", "lastName", "email"];
|
||||
|
||||
const isRootAttribute = (attr?: string) =>
|
||||
attr && ROOT_ATTRIBUTES.includes(attr);
|
||||
|
||||
export const fieldName = (attribute: UserProfileAttributeMetadata) =>
|
||||
`${isRootAttribute(attribute.name) ? "" : "attributes."}${attribute.name}`;
|
|
@ -13,14 +13,15 @@ import {
|
|||
TextContent,
|
||||
TextVariants,
|
||||
} from "@patternfly/react-core";
|
||||
import { Form } from "react-router-dom";
|
||||
import { KeycloakTextInput } from "../keycloak-text-input/KeycloakTextInput";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { isBundleKey, unWrap } from "../../user/utils";
|
||||
import { CheckIcon } from "@patternfly/react-icons";
|
||||
import { useAlerts } from "../alert/Alerts";
|
||||
import { ReactNode, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Form } from "react-router-dom";
|
||||
import { label } from "ui-shared";
|
||||
|
||||
import { useAlerts } from "../alert/Alerts";
|
||||
import { KeycloakTextInput } from "../keycloak-text-input/KeycloakTextInput";
|
||||
import { UserAttribute } from "./UserDataTable";
|
||||
|
||||
type UserDataTableAttributeSearchFormProps = {
|
||||
|
@ -153,11 +154,7 @@ export function UserDataTableAttributeSearchForm({
|
|||
{profile.attributes?.map((option) => (
|
||||
<SelectOption
|
||||
key={option.name}
|
||||
value={
|
||||
(isBundleKey(option.displayName)
|
||||
? t(unWrap(option.displayName!))
|
||||
: option.displayName) || option.name
|
||||
}
|
||||
value={label(t, option.displayName!, option.name)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectAttributeKeyOpen(false);
|
||||
|
|
|
@ -2,10 +2,12 @@ import type GroupRepresentation from "@keycloak/keycloak-admin-client/lib/defs/g
|
|||
import RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation";
|
||||
import type { UserProfileMetadata } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||
import { AlertVariant, PageSection } from "@patternfly/react-core";
|
||||
import { TFunction } from "i18next";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { isUserProfileError, setUserProfileServerError } from "ui-shared";
|
||||
|
||||
import { adminClient } from "../admin-client";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
|
@ -15,7 +17,6 @@ import { useRealm } from "../context/realm-context/RealmContext";
|
|||
import { useFetch } from "../utils/useFetch";
|
||||
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
|
||||
import { UserForm } from "./UserForm";
|
||||
import { isUserProfileError, setUserProfileServerError } from "ui-shared";
|
||||
import { UserFormFields, toUserRepresentation } from "./form-state";
|
||||
import { toUser } from "./routes/User";
|
||||
|
||||
|
@ -71,9 +72,8 @@ export default function CreateUser() {
|
|||
);
|
||||
} catch (error) {
|
||||
if (isUserProfileError(error)) {
|
||||
setUserProfileServerError(error, form.setError, (key, param) =>
|
||||
t(key as string, { ...param }),
|
||||
);
|
||||
setUserProfileServerError(error, form.setError, ((key, param) =>
|
||||
t(key as string, param as any)) as TFunction);
|
||||
} else {
|
||||
addError("userCreateError", error);
|
||||
}
|
||||
|
|
|
@ -14,11 +14,13 @@ import {
|
|||
Tooltip,
|
||||
} from "@patternfly/react-core";
|
||||
import { InfoCircleIcon } from "@patternfly/react-icons";
|
||||
import { TFunction } from "i18next";
|
||||
import { useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { isUserProfileError, setUserProfileServerError } from "ui-shared";
|
||||
|
||||
import { adminClient } from "../admin-client";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
|
@ -158,9 +160,8 @@ export default function EditUser() {
|
|||
refresh();
|
||||
} catch (error) {
|
||||
if (isUserProfileError(error)) {
|
||||
setUserProfileServerError(error, form.setError, (key, param) =>
|
||||
t(key as string, { ...param }),
|
||||
);
|
||||
setUserProfileServerError(error, form.setError, ((key, param) =>
|
||||
t(key as string, param as any)) as TFunction);
|
||||
} else {
|
||||
addError("userCreateError", error);
|
||||
}
|
||||
|
|
|
@ -12,11 +12,13 @@ import {
|
|||
InputGroup,
|
||||
Switch,
|
||||
} from "@patternfly/react-core";
|
||||
import { TFunction } from "i18next";
|
||||
import { useState } from "react";
|
||||
import { Controller, UseFormReturn } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { HelpItem, UserProfileFields } from "ui-shared";
|
||||
|
||||
import { adminClient } from "../admin-client";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { FormAccess } from "../components/form/FormAccess";
|
||||
|
@ -218,7 +220,10 @@ export const UserForm = ({
|
|||
userProfileMetadata={userProfileMetadata}
|
||||
hideReadOnly={!user}
|
||||
supportedLocales={realm.supportedLocales || []}
|
||||
t={(key: unknown, params) => t(key as string, { ...params })}
|
||||
t={
|
||||
((key: unknown, params) =>
|
||||
t(key as string, params as any)) as TFunction
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
|
|
@ -1,30 +1,2 @@
|
|||
import { UserProfileAttributeMetadata } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||
import { TFunction } from "i18next";
|
||||
|
||||
export const isBundleKey = (displayName?: string) =>
|
||||
displayName?.includes("${");
|
||||
export const unWrap = (key: string) => key.substring(2, key.length - 1);
|
||||
|
||||
export const label = (
|
||||
text: string | undefined,
|
||||
fallback: string | undefined,
|
||||
t: TFunction,
|
||||
) => (isBundleKey(text) ? t(unWrap(text!)) : text) || fallback;
|
||||
|
||||
export const labelAttribute = (
|
||||
attribute: UserProfileAttributeMetadata,
|
||||
t: TFunction,
|
||||
) => label(attribute.displayName, attribute.name, t);
|
||||
|
||||
const ROOT_ATTRIBUTES = ["username", "firstName", "lastName", "email"];
|
||||
|
||||
export const isRootAttribute = (attr?: string) =>
|
||||
attr && ROOT_ATTRIBUTES.includes(attr);
|
||||
|
||||
export const fieldName = (attribute: UserProfileAttributeMetadata) =>
|
||||
isRootAttribute(attribute.name)
|
||||
? attribute.name
|
||||
: `attributes.${attribute.name}`;
|
||||
|
||||
export const isLightweightUser = (userId?: string) =>
|
||||
userId?.startsWith("lightweight-");
|
||||
|
|
|
@ -40,13 +40,15 @@
|
|||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@keycloak/keycloak-admin-client": "workspace:*",
|
||||
"@patternfly/react-core": "^4.278.0",
|
||||
"@patternfly/react-icons": "^4.93.7",
|
||||
"@keycloak/keycloak-admin-client": "workspace:*",
|
||||
"i18next": "^23.7.6",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hook-form": "7.48.2"
|
||||
"react-hook-form": "7.48.2",
|
||||
"react-i18next": "^13.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
|
|
|
@ -18,6 +18,7 @@ export { UserProfileFields } from "./user-profile/UserProfileFields";
|
|||
export {
|
||||
setUserProfileServerError,
|
||||
isUserProfileError,
|
||||
label,
|
||||
} from "./user-profile/utils";
|
||||
export type { UserFormFields } from "./user-profile/utils";
|
||||
export { ScrollForm, mainPageContentId } from "./scroll-form/ScrollForm";
|
||||
|
|
|
@ -6,16 +6,13 @@ import {
|
|||
TextInputProps,
|
||||
} from "@patternfly/react-core";
|
||||
import { MinusCircleIcon, PlusCircleIcon } from "@patternfly/react-icons";
|
||||
import { type TFunction } from "i18next";
|
||||
import { Fragment, useEffect, useMemo } from "react";
|
||||
import { FieldPath, UseFormReturn, useWatch } from "react-hook-form";
|
||||
|
||||
import { UserProfileFieldProps } from "./UserProfileFields";
|
||||
import { UserProfileGroup } from "./UserProfileGroup";
|
||||
import {
|
||||
TranslationFunction,
|
||||
UserFormFields,
|
||||
fieldName,
|
||||
labelAttribute,
|
||||
} from "./utils";
|
||||
import { UserFormFields, fieldName, labelAttribute } from "./utils";
|
||||
|
||||
export const MultiInputComponent = ({
|
||||
t,
|
||||
|
@ -37,7 +34,7 @@ export const MultiInputComponent = ({
|
|||
);
|
||||
|
||||
export type MultiLineInputProps = Omit<TextInputProps, "form"> & {
|
||||
t: TranslationFunction;
|
||||
t: TFunction;
|
||||
name: FieldPath<UserFormFields>;
|
||||
form: UseFormReturn<UserFormFields>;
|
||||
addButtonLabel?: string;
|
||||
|
|
|
@ -4,8 +4,10 @@ import {
|
|||
UserProfileMetadata,
|
||||
} from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||
import { Text } from "@patternfly/react-core";
|
||||
import { TFunction } from "i18next";
|
||||
import { ReactNode, useMemo } from "react";
|
||||
import { FieldPath, UseFormReturn } from "react-hook-form";
|
||||
|
||||
import { ScrollForm } from "../main";
|
||||
import { LocaleSelector } from "./LocaleSelector";
|
||||
import { MultiInputComponent } from "./MultiInputComponent";
|
||||
|
@ -13,13 +15,7 @@ import { OptionComponent } from "./OptionsComponent";
|
|||
import { SelectComponent } from "./SelectComponent";
|
||||
import { TextAreaComponent } from "./TextAreaComponent";
|
||||
import { TextComponent } from "./TextComponent";
|
||||
import {
|
||||
TranslationFunction,
|
||||
UserFormFields,
|
||||
fieldName,
|
||||
isRootAttribute,
|
||||
label,
|
||||
} from "./utils";
|
||||
import { UserFormFields, fieldName, isRootAttribute, label } from "./utils";
|
||||
|
||||
export type UserProfileError = {
|
||||
responseData: { errors?: { errorMessage: string }[] };
|
||||
|
@ -51,7 +47,7 @@ const INPUT_TYPES = [
|
|||
export type InputType = (typeof INPUT_TYPES)[number];
|
||||
|
||||
export type UserProfileFieldProps = {
|
||||
t: TranslationFunction;
|
||||
t: TFunction;
|
||||
form: UseFormReturn<UserFormFields>;
|
||||
inputType: InputType;
|
||||
attribute: UserProfileAttributeMetadata;
|
||||
|
@ -80,7 +76,7 @@ export const FIELDS: {
|
|||
} as const;
|
||||
|
||||
export type UserProfileFieldsProps = {
|
||||
t: TranslationFunction;
|
||||
t: TFunction;
|
||||
form: UseFormReturn<UserFormFields>;
|
||||
userProfileMetadata: UserProfileMetadata;
|
||||
supportedLocales: string[];
|
||||
|
@ -167,7 +163,7 @@ export const UserProfileFields = ({
|
|||
};
|
||||
|
||||
type FormFieldProps = {
|
||||
t: TranslationFunction;
|
||||
t: TFunction;
|
||||
form: UseFormReturn<UserFormFields>;
|
||||
supportedLocales: string[];
|
||||
attribute: UserProfileAttributeMetadata;
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { UserProfileAttributeMetadata } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||
import { FormGroup, InputGroup } from "@patternfly/react-core";
|
||||
import { TFunction } from "i18next";
|
||||
import { get } from "lodash-es";
|
||||
import { PropsWithChildren, ReactNode } from "react";
|
||||
import { UseFormReturn } from "react-hook-form";
|
||||
|
||||
import { HelpItem } from "../controls/HelpItem";
|
||||
import {
|
||||
TranslationFunction,
|
||||
UserFormFields,
|
||||
fieldName,
|
||||
isRequiredAttribute,
|
||||
|
@ -13,7 +14,7 @@ import {
|
|||
} from "./utils";
|
||||
|
||||
export type UserProfileGroupProps = {
|
||||
t: TranslationFunction;
|
||||
t: TFunction;
|
||||
form: UseFormReturn<UserFormFields>;
|
||||
attribute: UserProfileAttributeMetadata;
|
||||
renderer?: (attribute: UserProfileAttributeMetadata) => ReactNode;
|
||||
|
@ -39,7 +40,9 @@ export const UserProfileGroup = ({
|
|||
fieldId={attribute.name}
|
||||
isRequired={isRequiredAttribute(attribute)}
|
||||
validated={get(errors, fieldName(attribute.name)) ? "error" : "default"}
|
||||
helperTextInvalid={t(get(errors, fieldName(attribute.name))?.message)}
|
||||
helperTextInvalid={t(
|
||||
get(errors, fieldName(attribute.name))?.message as string,
|
||||
)}
|
||||
labelIcon={
|
||||
helpText ? (
|
||||
<HelpItem helpText={helpText} fieldLabelId={attribute.name!} />
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { UserProfileAttributeMetadata } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
|
||||
import UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import { TFunction } from "i18next";
|
||||
import { FieldPath } from "react-hook-form";
|
||||
|
||||
export type KeyValueType = { key: string; value: string };
|
||||
|
@ -23,18 +24,17 @@ export type UserProfileError = {
|
|||
responseData: ErrorArray | FieldError;
|
||||
};
|
||||
|
||||
export const isBundleKey = (displayName?: string) =>
|
||||
displayName?.includes("${");
|
||||
const isBundleKey = (displayName?: string) => displayName?.includes("${");
|
||||
export const unWrap = (key: string) => key.substring(2, key.length - 1);
|
||||
|
||||
export const label = (
|
||||
t: TranslationFunction,
|
||||
t: TFunction,
|
||||
text: string | undefined,
|
||||
fallback: string | undefined,
|
||||
) => (isBundleKey(text) ? t(unWrap(text!)) : text) || fallback;
|
||||
|
||||
export const labelAttribute = (
|
||||
t: TranslationFunction,
|
||||
t: TFunction,
|
||||
attribute: UserProfileAttributeMetadata,
|
||||
) => label(t, attribute.displayName, attribute.name);
|
||||
|
||||
|
@ -51,7 +51,7 @@ export const fieldName = (name?: string) =>
|
|||
export function setUserProfileServerError<T>(
|
||||
error: UserProfileError,
|
||||
setError: (field: keyof T, params: object) => void,
|
||||
t: TranslationFunction,
|
||||
t: TFunction,
|
||||
) {
|
||||
(
|
||||
((error.responseData as ErrorArray).errors !== undefined
|
||||
|
@ -152,5 +152,3 @@ function isFieldError(error: unknown): error is FieldError {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
export type TranslationFunction = (key: unknown, params?: object) => string;
|
||||
|
|
|
@ -439,6 +439,9 @@ importers:
|
|||
'@patternfly/react-icons':
|
||||
specifier: ^4.93.7
|
||||
version: 4.93.7(react-dom@18.2.0)(react@18.2.0)
|
||||
i18next:
|
||||
specifier: ^23.7.6
|
||||
version: 23.7.7
|
||||
lodash-es:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
|
@ -451,6 +454,9 @@ importers:
|
|||
react-hook-form:
|
||||
specifier: 7.48.2
|
||||
version: 7.48.2(react@18.2.0)
|
||||
react-i18next:
|
||||
specifier: ^13.5.0
|
||||
version: 13.5.0(i18next@23.7.7)(react-dom@18.2.0)(react@18.2.0)
|
||||
devDependencies:
|
||||
'@types/lodash-es':
|
||||
specifier: ^4.17.12
|
||||
|
|
Loading…
Reference in a new issue