Migrate authenticate settings and policy detail to react-hook-form
v7 (#3984)
This commit is contained in:
parent
498034ee2e
commit
e0246c70d4
17 changed files with 250 additions and 99 deletions
|
@ -3,7 +3,7 @@ import { ActionGroup, ActionGroupProps, Button } from "@patternfly/react-core";
|
|||
|
||||
type SaveResetProps = ActionGroupProps & {
|
||||
name: string;
|
||||
save: () => void;
|
||||
save?: () => void;
|
||||
reset: () => void;
|
||||
isActive?: boolean;
|
||||
};
|
||||
|
@ -18,7 +18,12 @@ export const SaveReset = ({
|
|||
const { t } = useTranslation("common");
|
||||
return (
|
||||
<ActionGroup {...rest}>
|
||||
<Button isDisabled={!isActive} data-testid={name + "Save"} onClick={save}>
|
||||
<Button
|
||||
isDisabled={!isActive}
|
||||
data-testid={name + "Save"}
|
||||
onClick={save}
|
||||
type={save ? "button" : "submit"}
|
||||
>
|
||||
{t("save")}
|
||||
</Button>
|
||||
<Button
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
Switch,
|
||||
} from "@patternfly/react-core";
|
||||
import { useState } from "react";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form-v7";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import type ClientRepresentation from "@keycloak/keycloak-admin-client/lib/defs/clientRepresentation";
|
||||
|
@ -213,7 +213,7 @@ export const AuthorizationEvaluate = ({ client }: Props) => {
|
|||
control={control}
|
||||
defaultValue={[]}
|
||||
rules={{ validate: (value) => (value || "").length > 0 }}
|
||||
render={({ field }) => (
|
||||
render={(field) => (
|
||||
<Select
|
||||
placeholderText={t("selectARole")}
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
|
@ -314,7 +314,8 @@ export const AuthorizationEvaluate = ({ client }: Props) => {
|
|||
<KeycloakTextInput
|
||||
id="alias"
|
||||
data-testid="alias"
|
||||
{...register("alias", { required: true })}
|
||||
name="alias"
|
||||
ref={register({ required: true })}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -331,7 +332,7 @@ export const AuthorizationEvaluate = ({ client }: Props) => {
|
|||
name="authScopes"
|
||||
defaultValue={[]}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
render={(field) => (
|
||||
<Select
|
||||
toggleId="authScopes"
|
||||
onToggle={setScopesDropdownOpen}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { FormGroup, Radio } from "@patternfly/react-core";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
|
@ -35,7 +35,7 @@ export const DecisionStrategySelect = ({
|
|||
data-testid="decisionStrategy"
|
||||
defaultValue={DECISION_STRATEGY[0]}
|
||||
control={control}
|
||||
render={(field) => (
|
||||
render={({ field }) => (
|
||||
<>
|
||||
{(isLimited
|
||||
? DECISION_STRATEGY.slice(0, 2)
|
|
@ -1,6 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form-v7";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
|
@ -20,7 +20,7 @@ import { SaveReset } from "../advanced/SaveReset";
|
|||
import { ImportDialog } from "./ImportDialog";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { DecisionStrategySelect } from "./DecisionStragegySelect";
|
||||
import { DecisionStrategySelect } from "./DecisionStrategySelect";
|
||||
|
||||
const POLICY_ENFORCEMENT_MODES = [
|
||||
"ENFORCING",
|
||||
|
@ -28,12 +28,17 @@ const POLICY_ENFORCEMENT_MODES = [
|
|||
"DISABLED",
|
||||
] as const;
|
||||
|
||||
export type FormFields = Omit<
|
||||
ResourceServerRepresentation,
|
||||
"scopes" | "resources"
|
||||
>;
|
||||
|
||||
export const AuthorizationSettings = ({ clientId }: { clientId: string }) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const [resource, setResource] = useState<ResourceServerRepresentation>();
|
||||
const [importDialog, toggleImportDialog] = useToggle();
|
||||
|
||||
const form = useForm<ResourceServerRepresentation>({});
|
||||
const form = useForm<FormFields>({});
|
||||
const { control, reset, handleSubmit } = form;
|
||||
|
||||
const { adminClient } = useAdminClient();
|
||||
|
@ -58,7 +63,7 @@ export const AuthorizationSettings = ({ clientId }: { clientId: string }) => {
|
|||
}
|
||||
};
|
||||
|
||||
const save = async (resource: ResourceServerRepresentation) => {
|
||||
const onSubmit = async (resource: ResourceServerRepresentation) => {
|
||||
try {
|
||||
await adminClient.clients.updateResourceServer(
|
||||
{ id: clientId },
|
||||
|
@ -82,7 +87,11 @@ export const AuthorizationSettings = ({ clientId }: { clientId: string }) => {
|
|||
closeDialog={toggleImportDialog}
|
||||
/>
|
||||
)}
|
||||
<FormAccess role="view-clients" isHorizontal>
|
||||
<FormAccess
|
||||
role="view-clients"
|
||||
isHorizontal
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
>
|
||||
<FormGroup
|
||||
label={t("import")}
|
||||
fieldId="import"
|
||||
|
@ -114,16 +123,16 @@ export const AuthorizationSettings = ({ clientId }: { clientId: string }) => {
|
|||
data-testid="policyEnforcementMode"
|
||||
defaultValue={POLICY_ENFORCEMENT_MODES[0]}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<>
|
||||
{POLICY_ENFORCEMENT_MODES.map((mode) => (
|
||||
<Radio
|
||||
id={mode}
|
||||
key={mode}
|
||||
data-testid={mode}
|
||||
isChecked={value === mode}
|
||||
isChecked={field.value === mode}
|
||||
name="policyEnforcementMode"
|
||||
onChange={() => onChange(mode)}
|
||||
onChange={() => field.onChange(mode)}
|
||||
label={t(`policyEnforcementModes.${mode}`)}
|
||||
className="pf-u-mb-md"
|
||||
/>
|
||||
|
@ -151,13 +160,13 @@ export const AuthorizationSettings = ({ clientId }: { clientId: string }) => {
|
|||
data-testid="allowRemoteResourceManagement"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Switch
|
||||
id="allowRemoteResourceManagement"
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isChecked={field.value}
|
||||
onChange={field.onChange}
|
||||
aria-label={t("allowRemoteResourceManagement")}
|
||||
/>
|
||||
)}
|
||||
|
@ -165,7 +174,6 @@ export const AuthorizationSettings = ({ clientId }: { clientId: string }) => {
|
|||
</FormGroup>
|
||||
<SaveReset
|
||||
name="authenticationSettings"
|
||||
save={() => handleSubmit(save)()}
|
||||
reset={() => reset(resource)}
|
||||
isActive
|
||||
/>
|
||||
|
|
|
@ -4,7 +4,7 @@ import { useTranslation } from "react-i18next";
|
|||
import { HelpItem } from "../../../components/help-enabler/HelpItem";
|
||||
import { useParams } from "../../../utils/useParams";
|
||||
import type { PolicyDetailsParams } from "../../routes/PolicyDetails";
|
||||
import { DecisionStrategySelect } from "../DecisionStragegySelect";
|
||||
import { DecisionStrategySelect } from "../DecisionStrategySelect";
|
||||
import { ResourcesPolicySelect } from "../ResourcesPolicySelect";
|
||||
|
||||
export const Aggregate = () => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useState } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import {
|
||||
SelectOption,
|
||||
FormGroup,
|
||||
|
@ -84,23 +84,25 @@ export const Client = () => {
|
|||
rules={{
|
||||
validate: (value) => value.length > 0,
|
||||
}}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId="clients"
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
onToggle={(open) => setOpen(open)}
|
||||
isOpen={open}
|
||||
selections={value}
|
||||
selections={field.value}
|
||||
onFilter={(_, value) => {
|
||||
setSearch(value);
|
||||
return convert(clients);
|
||||
}}
|
||||
onSelect={(_, v) => {
|
||||
const option = v.toString();
|
||||
if (value.includes(option)) {
|
||||
onChange(value.filter((item: string) => item !== option));
|
||||
if (field.value.includes(option)) {
|
||||
field.onChange(
|
||||
field.value.filter((item: string) => item !== option)
|
||||
);
|
||||
} else {
|
||||
onChange([...value, option]);
|
||||
field.onChange([...field.value, option]);
|
||||
}
|
||||
setOpen(false);
|
||||
}}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext, Controller } from "react-hook-form";
|
||||
import { useFormContext, Controller } from "react-hook-form-v7";
|
||||
import { FormGroup, Button, Checkbox } from "@patternfly/react-core";
|
||||
import { MinusCircleIcon } from "@patternfly/react-icons";
|
||||
import {
|
||||
|
@ -76,13 +76,15 @@ export const ClientScope = () => {
|
|||
validate: (value: RequiredIdValue[]) =>
|
||||
value.filter((c) => c.id).length > 0,
|
||||
}}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<>
|
||||
{open && (
|
||||
<AddScopeDialog
|
||||
clientScopes={scopes.filter(
|
||||
(scope) =>
|
||||
!value.map((c: RequiredIdValue) => c.id).includes(scope.id!)
|
||||
!field.value
|
||||
.map((c: RequiredIdValue) => c.id)
|
||||
.includes(scope.id!)
|
||||
)}
|
||||
isClientScopesConditionType
|
||||
open={open}
|
||||
|
@ -92,8 +94,8 @@ export const ClientScope = () => {
|
|||
...selectedScopes,
|
||||
...scopes.map((s) => s.scope),
|
||||
]);
|
||||
onChange([
|
||||
...value,
|
||||
field.onChange([
|
||||
...field.value,
|
||||
...scopes
|
||||
.map((scope) => scope.scope)
|
||||
.map((item) => ({ id: item.id!, required: false })),
|
||||
|
@ -128,16 +130,16 @@ export const ClientScope = () => {
|
|||
<Td>{scope.name}</Td>
|
||||
<Td>
|
||||
<Controller
|
||||
name={`clientScopes[${index}].required`}
|
||||
name={`clientScopes.${index}.required`}
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
id="required"
|
||||
data-testid="standard"
|
||||
name="required"
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isChecked={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext, Controller } from "react-hook-form";
|
||||
import { useFormContext, Controller } from "react-hook-form-v7";
|
||||
import { MinusCircleIcon } from "@patternfly/react-icons";
|
||||
import { FormGroup, Button, Checkbox } from "@patternfly/react-core";
|
||||
import {
|
||||
|
@ -18,6 +18,11 @@ import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
|
|||
import { GroupPickerDialog } from "../../../components/group/GroupPickerDialog";
|
||||
import { KeycloakTextInput } from "../../../components/keycloak-text-input/KeycloakTextInput";
|
||||
|
||||
type GroupForm = {
|
||||
groups?: GroupValue[];
|
||||
groupsClaim: string;
|
||||
};
|
||||
|
||||
export type GroupValue = {
|
||||
id: string;
|
||||
extendChildren: boolean;
|
||||
|
@ -31,9 +36,7 @@ export const Group = () => {
|
|||
getValues,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useFormContext<{
|
||||
groups?: GroupValue[];
|
||||
}>();
|
||||
} = useFormContext<GroupForm>();
|
||||
const values = getValues("groups");
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
@ -73,9 +76,8 @@ export const Group = () => {
|
|||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="groupsClaim"
|
||||
name="groupsClaim"
|
||||
data-testid="groupsClaim"
|
||||
ref={register}
|
||||
{...register("groupsClaim")}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
|
@ -96,10 +98,10 @@ export const Group = () => {
|
|||
control={control}
|
||||
defaultValue={[]}
|
||||
rules={{
|
||||
validate: (value: GroupValue[]) =>
|
||||
value.filter(({ id }) => id).length > 0,
|
||||
validate: (value?: GroupValue[]) =>
|
||||
value && value.filter(({ id }) => id).length > 0,
|
||||
}}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<>
|
||||
{open && (
|
||||
<GroupPickerDialog
|
||||
|
@ -109,8 +111,8 @@ export const Group = () => {
|
|||
ok: "common:add",
|
||||
}}
|
||||
onConfirm={(groups) => {
|
||||
onChange([
|
||||
...value,
|
||||
field.onChange([
|
||||
...(field.value || []),
|
||||
...(groups || []).map(({ id }) => ({ id })),
|
||||
]);
|
||||
setSelectedGroups([...selectedGroups, ...(groups || [])]);
|
||||
|
@ -149,16 +151,16 @@ export const Group = () => {
|
|||
<Td>{group.path}</Td>
|
||||
<Td>
|
||||
<Controller
|
||||
name={`groups[${index}].extendChildren`}
|
||||
name={`groups.${index}.extendChildren`}
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
id="extendChildren"
|
||||
data-testid="standard"
|
||||
name="extendChildren"
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isChecked={field.value}
|
||||
onChange={field.onChange}
|
||||
isDisabled={group.subGroups?.length === 0}
|
||||
/>
|
||||
)}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { FormGroup } from "@patternfly/react-core";
|
||||
import { CodeEditor, Language } from "@patternfly/react-code-editor";
|
||||
|
||||
|
@ -25,13 +25,12 @@ export const JavaScript = () => {
|
|||
name="code"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<CodeEditor
|
||||
id="code"
|
||||
data-testid="code"
|
||||
type="text"
|
||||
onChange={onChange}
|
||||
code={value}
|
||||
onChange={field.onChange}
|
||||
code={field.value}
|
||||
height="600px"
|
||||
language={Language.javascript}
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import { FormGroup, Radio } from "@patternfly/react-core";
|
||||
|
||||
import { HelpItem } from "../../../components/help-enabler/HelpItem";
|
||||
|
@ -24,16 +24,16 @@ export const LogicSelector = () => {
|
|||
data-testid="logic"
|
||||
defaultValue={LOGIC_TYPES[0]}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<>
|
||||
{LOGIC_TYPES.map((type) => (
|
||||
<Radio
|
||||
id={type}
|
||||
key={type}
|
||||
data-testid={type}
|
||||
isChecked={value === type}
|
||||
isChecked={field.value === type}
|
||||
name="logic"
|
||||
onChange={() => onChange(type)}
|
||||
onChange={() => field.onChange(type)}
|
||||
label={t(`logicType.${type.toLowerCase()}`)}
|
||||
className="pf-u-mb-md"
|
||||
/>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { useFormContext } from "react-hook-form-v7";
|
||||
import { FormGroup, ValidatedOptions } from "@patternfly/react-core";
|
||||
|
||||
import { HelpItem } from "../../../components/help-enabler/HelpItem";
|
||||
|
@ -35,11 +35,9 @@ export const NameDescription = ({ prefix }: NameDescriptionProps) => {
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="kc-name"
|
||||
name="name"
|
||||
data-testid="name"
|
||||
ref={register({ required: true })}
|
||||
{...register("name", { required: true })}
|
||||
validated={
|
||||
errors.name ? ValidatedOptions.error : ValidatedOptions.default
|
||||
}
|
||||
|
@ -60,15 +58,13 @@ export const NameDescription = ({ prefix }: NameDescriptionProps) => {
|
|||
helperTextInvalid={errors.description?.message}
|
||||
>
|
||||
<KeycloakTextArea
|
||||
ref={register({
|
||||
{...register("description", {
|
||||
maxLength: {
|
||||
value: 255,
|
||||
message: t("common:maxLength", { length: 255 }),
|
||||
},
|
||||
})}
|
||||
type="text"
|
||||
id="kc-description"
|
||||
name="description"
|
||||
data-testid="description"
|
||||
validated={
|
||||
errors.description
|
||||
|
|
|
@ -8,7 +8,7 @@ import {
|
|||
PageSection,
|
||||
} from "@patternfly/react-core";
|
||||
import { FunctionComponent, useState } from "react";
|
||||
import { FormProvider, useForm } from "react-hook-form";
|
||||
import { FormProvider, useForm } from "react-hook-form-v7";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate } from "react-router-dom-v5-compat";
|
||||
|
||||
|
@ -105,7 +105,7 @@ export default function PolicyDetails() {
|
|||
[]
|
||||
);
|
||||
|
||||
const save = async (policy: Policy) => {
|
||||
const onSubmit = async (policy: Policy) => {
|
||||
// remove entries that only have the boolean set and no id
|
||||
policy.groups = policy.groups?.filter((g) => g.id);
|
||||
policy.clientScopes = policy.clientScopes?.filter((c) => c.id);
|
||||
|
@ -194,7 +194,7 @@ export default function PolicyDetails() {
|
|||
<PageSection variant="light">
|
||||
<FormAccess
|
||||
isHorizontal
|
||||
onSubmit={handleSubmit(save)}
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
role="view-clients"
|
||||
>
|
||||
<FormProvider {...form}>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext } from "react-hook-form";
|
||||
import { useFormContext } from "react-hook-form-v7";
|
||||
import { FormGroup } from "@patternfly/react-core";
|
||||
|
||||
import { HelpItem } from "../../../components/help-enabler/HelpItem";
|
||||
|
@ -28,11 +28,9 @@ export const Regex = () => {
|
|||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
type="text"
|
||||
id="targetClaim"
|
||||
name="targetClaim"
|
||||
data-testid="targetClaim"
|
||||
ref={register({ required: true })}
|
||||
{...register("targetClaim", { required: true })}
|
||||
validated={errors.targetClaim ? "error" : "default"}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
@ -50,10 +48,8 @@ export const Regex = () => {
|
|||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
ref={register({ required: true })}
|
||||
type="text"
|
||||
{...register("pattern", { required: true })}
|
||||
id="pattern"
|
||||
name="pattern"
|
||||
data-testid="regexPattern"
|
||||
validated={errors.pattern ? "error" : "default"}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useFormContext, Controller } from "react-hook-form";
|
||||
import { useFormContext, Controller } from "react-hook-form-v7";
|
||||
import { FormGroup, Button, Checkbox } from "@patternfly/react-core";
|
||||
import { MinusCircleIcon } from "@patternfly/react-icons";
|
||||
import {
|
||||
|
@ -77,18 +77,18 @@ export const Role = () => {
|
|||
control={control}
|
||||
defaultValue={[]}
|
||||
rules={{
|
||||
validate: (value: RequiredIdValue[]) =>
|
||||
value.filter((c) => c.id).length > 0,
|
||||
validate: (value?: RequiredIdValue[]) =>
|
||||
value && value.filter((c) => c.id).length > 0,
|
||||
}}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<>
|
||||
{open && (
|
||||
<AddRoleMappingModal
|
||||
id="role"
|
||||
type="roles"
|
||||
onAssign={(rows) => {
|
||||
onChange([
|
||||
...value,
|
||||
field.onChange([
|
||||
...(field.value || []),
|
||||
...rows.map((row) => ({ id: row.role.id })),
|
||||
]);
|
||||
setSelectedRoles([...selectedRoles, ...rows]);
|
||||
|
@ -129,16 +129,16 @@ export const Role = () => {
|
|||
</Td>
|
||||
<Td>
|
||||
<Controller
|
||||
name={`roles[${index}].required`}
|
||||
name={`roles.${index}.required`}
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
id="required"
|
||||
data-testid="standard"
|
||||
name="required"
|
||||
isChecked={value}
|
||||
onChange={onChange}
|
||||
isChecked={field.value}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import {
|
||||
DatePicker,
|
||||
Flex,
|
||||
|
@ -59,15 +59,20 @@ const DateTime = ({ name }: { name: string }) => {
|
|||
defaultValue=""
|
||||
control={control}
|
||||
rules={{ required: true }}
|
||||
render={({ onChange, value }) => {
|
||||
const dateTime = value.match(DATE_TIME_FORMAT) || ["", "", "0", "00"];
|
||||
render={({ field }) => {
|
||||
const dateTime = field.value.match(DATE_TIME_FORMAT) || [
|
||||
"",
|
||||
"",
|
||||
"0",
|
||||
"00",
|
||||
];
|
||||
return (
|
||||
<Split hasGutter id={name}>
|
||||
<SplitItem>
|
||||
<DatePicker
|
||||
value={dateTime[1]}
|
||||
onChange={(_, date) => {
|
||||
onChange(parseDate(value, date));
|
||||
field.onChange(parseDate(field.value, date));
|
||||
}}
|
||||
/>
|
||||
</SplitItem>
|
||||
|
@ -75,7 +80,7 @@ const DateTime = ({ name }: { name: string }) => {
|
|||
<TimePicker
|
||||
time={`${dateTime[2]}:${dateTime[3]}`}
|
||||
onChange={(_, hour, minute) =>
|
||||
onChange(parseTime(value, hour, minute))
|
||||
field.onChange(parseTime(field.value, hour, minute))
|
||||
}
|
||||
is24Hour
|
||||
/>
|
||||
|
@ -102,17 +107,17 @@ const NumberControl = ({ name, min, max }: NumberControlProps) => {
|
|||
name={name}
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
render={({ field }) => (
|
||||
<NumberInput
|
||||
id={name}
|
||||
value={value}
|
||||
value={field.value}
|
||||
min={min}
|
||||
max={max}
|
||||
onPlus={() => onChange(Number(value) + 1)}
|
||||
onMinus={() => onChange(Number(value) - 1)}
|
||||
onPlus={() => field.onChange(Number(field.value) + 1)}
|
||||
onMinus={() => field.onChange(Number(field.value) - 1)}
|
||||
onChange={(event) => {
|
||||
const newValue = Number(event.currentTarget.value);
|
||||
onChange(setValue(!isNaN(newValue) ? newValue : 0));
|
||||
field.onChange(setValue(!isNaN(newValue) ? newValue : 0));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -149,7 +154,10 @@ const FromTo = ({ name, ...rest }: NumberControlProps) => {
|
|||
|
||||
export const Time = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
const { getValues, errors } = useFormContext();
|
||||
const {
|
||||
getValues,
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
const [repeat, setRepeat] = useState(getValues("month"));
|
||||
return (
|
||||
<>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { UserSelect } from "../../../components/users/UserSelect";
|
||||
import { UserSelect } from "../../../components/users/hook-form-v7/UserSelect";
|
||||
|
||||
export const User = () => (
|
||||
<UserSelect
|
||||
|
|
132
apps/admin-ui/src/components/users/hook-form-v7/UserSelect.tsx
Normal file
132
apps/admin-ui/src/components/users/hook-form-v7/UserSelect.tsx
Normal file
|
@ -0,0 +1,132 @@
|
|||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form-v7";
|
||||
import {
|
||||
SelectOption,
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectVariant,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import type { UserQuery } from "@keycloak/keycloak-admin-client/lib/resources/users";
|
||||
import type { ComponentProps } from "../../dynamic/components";
|
||||
|
||||
import { useAdminClient, useFetch } from "../../../context/auth/AdminClient";
|
||||
import { HelpItem } from "../../help-enabler/HelpItem";
|
||||
import useToggle from "../../../utils/useToggle";
|
||||
|
||||
type UserSelectProps = ComponentProps & {
|
||||
variant?: SelectVariant;
|
||||
isRequired?: boolean;
|
||||
};
|
||||
|
||||
export const UserSelect = ({
|
||||
name,
|
||||
label,
|
||||
helpText,
|
||||
defaultValue,
|
||||
isRequired,
|
||||
variant = SelectVariant.typeaheadMulti,
|
||||
}: UserSelectProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
const {
|
||||
control,
|
||||
getValues,
|
||||
formState: { errors },
|
||||
} = useFormContext();
|
||||
const values: string[] | undefined = getValues(name!);
|
||||
|
||||
const [open, toggleOpen] = useToggle();
|
||||
const [users, setUsers] = useState<(UserRepresentation | undefined)[]>([]);
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const { adminClient } = useAdminClient();
|
||||
|
||||
useFetch(
|
||||
() => {
|
||||
const params: UserQuery = {
|
||||
max: 20,
|
||||
};
|
||||
if (search) {
|
||||
params.username = search;
|
||||
}
|
||||
|
||||
if (values?.length && !search) {
|
||||
return Promise.all(
|
||||
values.map((id: string) => adminClient.users.findOne({ id }))
|
||||
);
|
||||
}
|
||||
return adminClient.users.find(params);
|
||||
},
|
||||
setUsers,
|
||||
[search]
|
||||
);
|
||||
|
||||
const convert = (clients: (UserRepresentation | undefined)[]) =>
|
||||
clients
|
||||
.filter((c) => c !== undefined)
|
||||
.map((option) => (
|
||||
<SelectOption
|
||||
key={option!.id}
|
||||
value={option!.id}
|
||||
selected={values?.includes(option!.id!)}
|
||||
>
|
||||
{option!.username}
|
||||
</SelectOption>
|
||||
));
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
label={t(label!)}
|
||||
isRequired={isRequired}
|
||||
labelIcon={
|
||||
<HelpItem helpText={helpText!} fieldLabelId={`clients:${label}`} />
|
||||
}
|
||||
fieldId={name!}
|
||||
validated={errors[name!] ? "error" : "default"}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<Controller
|
||||
name={name!}
|
||||
defaultValue={defaultValue}
|
||||
control={control}
|
||||
rules={
|
||||
isRequired && variant === SelectVariant.typeaheadMulti
|
||||
? { validate: (value) => value.length > 0 }
|
||||
: isRequired
|
||||
? { required: true }
|
||||
: {}
|
||||
}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
toggleId={name!}
|
||||
variant={variant}
|
||||
placeholderText={t("selectAUser")}
|
||||
onToggle={toggleOpen}
|
||||
isOpen={open}
|
||||
selections={field.value}
|
||||
onFilter={(_, value) => {
|
||||
setSearch(value);
|
||||
return convert(users);
|
||||
}}
|
||||
onSelect={(_, v) => {
|
||||
const option = v.toString();
|
||||
if (field.value.includes(option)) {
|
||||
field.onChange(
|
||||
field.value.filter((item: string) => item !== option)
|
||||
);
|
||||
} else {
|
||||
field.onChange([...field.value, option]);
|
||||
}
|
||||
toggleOpen();
|
||||
}}
|
||||
aria-label={t(name!)}
|
||||
>
|
||||
{convert(users)}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
Loading…
Reference in a new issue