added react hook form to do forms (#99)

* added react hook form to do forms

* removed unnessary property
This commit is contained in:
Erik Jan de Wit 2020-09-17 15:51:40 +02:00 committed by GitHub
parent b87bd2ca76
commit 338e177c51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 295 additions and 201 deletions

View file

@ -25,6 +25,7 @@
"keycloak-js": "^11.0.0",
"react": "^16.8.5",
"react-dom": "^16.8.5",
"react-hook-form": "^6.8.2",
"react-i18next": "^11.7.0",
"react-router-dom": "^5.2.0"
},

View file

@ -1,46 +1,35 @@
import React, { FormEvent } from "react";
import React from "react";
import { FormGroup, TextInput } from "@patternfly/react-core";
import { ClientRepresentation } from "./models/client-model";
import { FieldElement, ValidationRules, Ref } from "react-hook-form";
import { useTranslation } from "react-i18next";
type ClientDescriptionProps = {
onChange: (value: string, event: FormEvent<HTMLInputElement>) => void;
client: ClientRepresentation;
register<TFieldElement extends FieldElement>(
rules?: ValidationRules
): (ref: (TFieldElement & Ref) | null) => void;
};
export const ClientDescription = ({
client,
onChange,
}: ClientDescriptionProps) => {
export const ClientDescription = ({ register }: ClientDescriptionProps) => {
const { t } = useTranslation("clients");
return (
<>
<FormGroup label={t("clientID")} fieldId="kc-client-id">
<TextInput
ref={register()}
type="text"
id="kc-client-id"
name="clientId"
value={client.clientId}
onChange={onChange}
/>
</FormGroup>
<FormGroup label={t("name")} fieldId="kc-name">
<TextInput
type="text"
id="kc-name"
name="name"
value={client.name}
onChange={onChange}
/>
<TextInput ref={register()} type="text" id="kc-name" name="name" />
</FormGroup>
<FormGroup label={t("description")} fieldId="kc-description">
<TextInput
ref={register()}
type="text"
id="kc-description"
name="description"
value={client.description}
onChange={onChange}
/>
</FormGroup>
</>

View file

@ -1,4 +1,5 @@
import React, { useState, FormEvent, useContext } from "react";
import React, { useState, useContext } from "react";
import { useHistory } from "react-router-dom";
import {
Text,
PageSection,
@ -6,19 +7,25 @@ import {
Divider,
Wizard,
AlertVariant,
WizardFooter,
WizardContextConsumer,
Button,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { useForm } from "react-hook-form";
import { HttpClientContext } from "../../http-service/HttpClientContext";
import { Step1 } from "./Step1";
import { Step2 } from "./Step2";
import { ClientRepresentation } from "../models/client-model";
import { useAlerts } from "../../components/alert/Alerts";
import { useTranslation } from "react-i18next";
import { RealmContext } from "../../components/realm-context/RealmContext";
export const NewClientForm = () => {
const { t } = useTranslation("clients");
const httpClient = useContext(HttpClientContext)!;
const { realm } = useContext(RealmContext);
const history = useHistory();
const [client, setClient] = useState<ClientRepresentation>({
protocol: "",
@ -27,8 +34,13 @@ export const NewClientForm = () => {
description: "",
publicClient: false,
authorizationServicesEnabled: false,
serviceAccountsEnabled: false,
implicitFlowEnabled: false,
directAccessGrantsEnabled: false,
standardFlowEnabled: false,
});
const [add, Alerts] = useAlerts();
const methods = useForm<ClientRepresentation>({ defaultValues: client });
const save = async () => {
try {
@ -39,18 +51,46 @@ export const NewClientForm = () => {
}
};
const handleInputChange = (
value: string | boolean,
event: FormEvent<HTMLInputElement>
) => {
const target = event.target;
const name = (target as HTMLInputElement).name;
setClient({
...client,
[name]: value,
});
};
const Footer = () => (
<WizardFooter>
<WizardContextConsumer>
{({ activeStep, onNext, onBack, onClose }) => {
return (
<>
<Button
variant="primary"
type="submit"
onClick={async () => {
if (await methods.trigger()) {
setClient({ ...client, ...methods.getValues() });
onNext();
}
}}
>
{activeStep.name === t("capabilityConfig")
? t("common:save")
: t("common:next")}
</Button>
<Button
variant="secondary"
onClick={() => {
setClient({ ...client, ...methods.getValues() });
methods.reset({ ...client, ...methods.getValues() });
onBack();
}}
isDisabled={activeStep.name === t("generalSettings")}
>
{t("common:back")}
</Button>
<Button variant="link" onClick={onClose}>
{t("common:cancel")}
</Button>
</>
);
}}
</WizardContextConsumer>
</WizardFooter>
);
const title = t("createClient");
return (
@ -64,19 +104,20 @@ export const NewClientForm = () => {
<Divider />
<PageSection variant="light">
<Wizard
onClose={() => history.push("/clients")}
navAriaLabel={`${title} steps`}
mainAriaLabel={`${title} content`}
steps={[
{
name: t("generalSettings"),
component: <Step1 onChange={handleInputChange} client={client} />,
component: <Step1 form={methods} />,
},
{
name: t("capabilityConfig"),
component: <Step2 onChange={handleInputChange} client={client} />,
nextButtonText: t("common:save"),
component: <Step2 form={methods} />,
},
]}
footer={<Footer />}
onSave={() => save()}
/>
</PageSection>

View file

@ -1,4 +1,4 @@
import React, { useState, FormEvent, useEffect, useContext } from "react";
import React, { useState, useEffect, useContext } from "react";
import {
FormGroup,
Form,
@ -6,20 +6,22 @@ import {
SelectVariant,
SelectOption,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { Controller, UseFormMethods } from "react-hook-form";
import { HttpClientContext } from "../../http-service/HttpClientContext";
import { sortProvider } from "../../util";
import { ServerInfoRepresentation } from "../models/server-info";
import { ClientRepresentation } from "../models/client-model";
import { ClientDescription } from "../ClientDescription";
type Step1Props = {
onChange: (value: string, event: FormEvent<HTMLInputElement>) => void;
client: ClientRepresentation;
form: UseFormMethods;
};
export const Step1 = ({ client, onChange }: Step1Props) => {
export const Step1 = ({ form }: Step1Props) => {
const httpClient = useContext(HttpClientContext)!;
const { t } = useTranslation();
const { errors, control, register } = form;
const [providers, setProviders] = useState<string[]>([]);
const [open, isOpen] = useState(false);
@ -38,37 +40,45 @@ export const Step1 = ({ client, onChange }: Step1Props) => {
return (
<Form isHorizontal>
<FormGroup label="Client Type" fieldId="kc-type" isRequired>
<Select
id="kc-type"
required
onToggle={() => isOpen(!open)}
onSelect={(_, value) => {
onChange(
value as string,
({
target: {
name: "protocol",
},
} as unknown) as FormEvent<HTMLInputElement>
);
isOpen(false);
}}
selections={client.protocol}
variant={SelectVariant.single}
aria-label="Select Encryption type"
isOpen={open}
>
{providers.map((option) => (
<SelectOption
key={option}
value={option || "Select an option"}
isPlaceholder={option === ""}
/>
))}
</Select>
<FormGroup
label="Client Type"
fieldId="kc-type"
isRequired
helperTextInvalid={t("common:required")}
validated={errors.protocol ? "error" : "default"}
>
<Controller
name="protocol"
defaultValue=""
control={control}
rules={{ required: true }}
render={({ onChange, value }) => (
<Select
id="kc-type"
required
onToggle={() => isOpen(!open)}
onSelect={(_, value, isPlaceholder) => {
onChange(isPlaceholder ? "" : (value as string));
isOpen(false);
}}
selections={value}
variant={SelectVariant.single}
aria-label="Select Encryption type"
isOpen={open}
>
{providers.map((option) => (
<SelectOption
selected={option === value}
key={option}
value={option === "" ? "Select an option" : option}
isPlaceholder={option === ""}
/>
))}
</Select>
)}
/>
</FormGroup>
<ClientDescription onChange={onChange} client={client} />
<ClientDescription register={register} />
</Form>
);
};

View file

@ -1,87 +1,123 @@
import React from "react";
import {
Form,
FormGroup,
Switch,
Checkbox,
Grid,
GridItem,
Form,
} from "@patternfly/react-core";
import React, { FormEvent } from "react";
import { ClientRepresentation } from "../models/client-model";
import { UseFormMethods, Controller } from "react-hook-form";
import { useTranslation } from "react-i18next";
type Step2Props = {
onChange: (
value: string | boolean,
event: FormEvent<HTMLInputElement>
) => void;
client: ClientRepresentation;
form: UseFormMethods;
};
export const Step2 = ({ client, onChange }: Step2Props) => (
<Form isHorizontal>
<FormGroup label="Client authentication" fieldId="kc-authentication">
<Switch
id="kc-authentication"
name="publicClient"
label="ON"
labelOff="OFF"
isChecked={client.publicClient}
onChange={onChange}
/>
</FormGroup>
<FormGroup label="Authentication" fieldId="kc-authorisation">
<Switch
id="kc-authorisation"
name="authorizationServicesEnabled"
label="ON"
labelOff="OFF"
isChecked={client.authorizationServicesEnabled}
onChange={onChange}
/>
</FormGroup>
<FormGroup label="Authentication flow" fieldId="kc-flow">
<Grid>
<GridItem span={6}>
<Checkbox
label="Standard flow"
aria-label="Enable standard flow"
id="kc-flow-standard"
name="standardFlowEnabled"
isChecked={client.standardFlowEnabled}
onChange={onChange}
/>
</GridItem>
<GridItem span={6}>
<Checkbox
label="Direct access"
aria-label="Enable Direct access"
id="kc-flow-direct"
name="directAccessGrantsEnabled"
isChecked={client.directAccessGrantsEnabled}
onChange={onChange}
/>
</GridItem>
<GridItem span={6}>
<Checkbox
label="Implicid flow"
aria-label="Enable implicid flow"
id="kc-flow-implicid"
name="implicitFlowEnabled"
isChecked={client.implicitFlowEnabled}
onChange={onChange}
/>
</GridItem>
<GridItem span={6}>
<Checkbox
label="Service account"
aria-label="Enable service account"
id="kc-flow-service-account"
name="serviceAccountsEnabled"
isChecked={client.serviceAccountsEnabled}
onChange={onChange}
/>
</GridItem>
</Grid>
</FormGroup>
</Form>
);
export const Step2 = ({ form }: Step2Props) => {
const { t } = useTranslation("clients");
return (
<Form isHorizontal>
<FormGroup label={t("clientAuthentication")} fieldId="kc-authentication">
<Controller
name="publicClient"
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-authentication"
name="publicClient"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup label={t("authentication")} fieldId="kc-authorisation">
<Controller
name="authorizationServicesEnabled"
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-authorisation"
name="authorizationServicesEnabled"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup label={t("authenticationFlow")} fieldId="kc-flow">
<Grid>
<GridItem span={6}>
<Controller
name="standardFlowEnabled"
control={form.control}
render={({ onChange, value }) => (
<Checkbox
label={t("standardFlow")}
aria-label={t("enableStandardFlow")}
id="kc-flow-standard"
name="standardFlowEnabled"
isChecked={value}
onChange={onChange}
/>
)}
/>
</GridItem>
<GridItem span={6}>
<Controller
name="directAccessGrantsEnabled"
control={form.control}
render={({ onChange, value }) => (
<Checkbox
label={t("directAccess")}
aria-label={t("enableDirectAccess")}
id="kc-flow-direct"
name="directAccessGrantsEnabled"
isChecked={value}
onChange={onChange}
/>
)}
/>
</GridItem>
<GridItem span={6}>
<Controller
name="implicitFlowEnabled"
control={form.control}
render={({ onChange, value }) => (
<Checkbox
label={t("implicidFlow")}
aria-label={t("enableImplicidFlow")}
id="kc-flow-implicid"
name="implicitFlowEnabled"
isChecked={value}
onChange={onChange}
/>
)}
/>
</GridItem>
<GridItem span={6}>
<Controller
name="serviceAccountsEnabled"
control={form.control}
render={({ onChange, value }) => (
<Checkbox
label={t("serviceAccount")}
aria-label={t("enableServiceAccount")}
id="kc-flow-service-account"
name="serviceAccountsEnabled"
isChecked={value}
onChange={onChange}
/>
)}
/>
</GridItem>
</Grid>
</FormGroup>
</Form>
);
};

View file

@ -1,4 +1,4 @@
import React, { useState, FormEvent, useContext } from "react";
import React, { useContext } from "react";
import {
PageSection,
Text,
@ -11,6 +11,7 @@ import {
Button,
AlertVariant,
} from "@patternfly/react-core";
import { useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { ClientRepresentation } from "../models/client-model";
@ -25,31 +26,27 @@ export const ImportForm = () => {
const { t } = useTranslation("clients");
const httpClient = useContext(HttpClientContext)!;
const { realm } = useContext(RealmContext);
const { register, handleSubmit, errors, setValue } = useForm<
ClientRepresentation
>();
const [add, Alerts] = useAlerts();
const defaultClient = {
protocol: "",
clientId: "",
name: "",
description: "",
};
const [client, setClient] = useState<ClientRepresentation>(defaultClient);
const handleFileChange = (value: string | File) => {
setClient({
...client,
...(value ? JSON.parse(value as string) : defaultClient),
const defaultClient = {
protocol: "",
clientId: "",
name: "",
description: "",
};
const obj = value ? JSON.parse(value as string) : defaultClient;
Object.keys(obj).forEach((k) => {
setValue(k, obj[k]);
});
};
const handleDescriptionChange = (
value: string,
event: FormEvent<HTMLInputElement>
) => {
const name = (event.target as HTMLInputElement).name;
setClient({ ...client, [name]: value });
};
const save = async () => {
const save = async (client: ClientRepresentation) => {
try {
await httpClient.doPost(`/admin/realms/${realm}/clients`, client);
add(t("clientImportSuccess"), AlertVariant.success);
@ -68,23 +65,20 @@ export const ImportForm = () => {
</PageSection>
<Divider />
<PageSection variant="light">
<Form isHorizontal>
<Form isHorizontal onSubmit={handleSubmit(save)}>
<JsonFileUpload id="realm-file" onChange={handleFileChange} />
<ClientDescription
onChange={handleDescriptionChange}
client={client}
/>
<ClientDescription register={register} />
<FormGroup label={t("type")} fieldId="kc-type">
<TextInput
type="text"
id="kc-type"
name="protocol"
value={client.protocol}
isReadOnly
ref={register()}
/>
</FormGroup>
<ActionGroup>
<Button variant="primary" onClick={() => save()}>
<Button variant="primary" type="submit">
{t("common:save")}
</Button>
<Button variant="link">{t("common:cancel")}</Button>

View file

@ -14,6 +14,17 @@
"clientImportError": "Could not import client",
"clientImportSuccess": "Client imported succeful",
"clientDeletedSucess": "The client has been deleted",
"clientDeleteError": "Could not delete client:"
"clientDeleteError": "Could not delete client:",
"clientAuthentication": "Client authentication",
"authentication": "Authentication",
"authenticationFlow": "Authentication flow",
"standardFlow": "Standard flow",
"enableStandardFlow": "Enable standard flow",
"directAccess": "Direct access",
"enableDirectAccess": "Enable direct access",
"implicidFlow": "Implicid flow",
"enableImplicidFlow": "Enable implicid flow",
"serviceAccount": "Service account",
"enableServiceAccount": "Enable service account"
}
}

View file

@ -6,15 +6,21 @@
"save": "Save",
"cancel": "Cancel",
"delete": "Delete",
"next": "Next",
"back": "Back",
"export": "Export",
"resourceFile": "Resource file",
"clearFile": "Clear this file",
"on": "On",
"off":"Off",
"signOut": "Sign out",
"manageAccount": "Manage account",
"serverInfo": "Server info",
"help": "Help",
"documentation": "Documentation",
"enableHelpMode": "Enable help mode"
"enableHelpMode": "Enable help mode",
"required": "Required field"
}
}

View file

@ -18,30 +18,27 @@ import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload
import { RealmRepresentation } from "../models/Realm";
import { HttpClientContext } from "../../http-service/HttpClientContext";
import { useAlerts } from "../../components/alert/Alerts";
import { useForm, Controller } from "react-hook-form";
export const NewRealmForm = () => {
const { t } = useTranslation("realm");
const httpClient = useContext(HttpClientContext)!;
const [add, Alerts] = useAlerts();
const defaultRealm = { id: "", realm: "", enabled: true };
const [realm, setRealm] = useState<RealmRepresentation>(defaultRealm);
const { register, handleSubmit, setValue, control } = useForm<
RealmRepresentation
>();
const handleFileChange = (value: string | File) => {
setRealm({
...realm,
...(value ? JSON.parse(value as string) : defaultRealm),
const defaultRealm = { id: "", realm: "", enabled: true };
const obj = value ? JSON.parse(value as string) : defaultRealm;
Object.keys(obj).forEach((k) => {
setValue(k, obj[k]);
});
};
const handleChange = (
value: string | boolean,
event: FormEvent<HTMLInputElement>
) => {
const name = (event.target as HTMLInputElement).name;
setRealm({ ...realm, [name]: value });
};
const save = async () => {
const save = async (realm: RealmRepresentation) => {
try {
await httpClient.doPost("/admin/realms", realm);
add(t("Realm created"), AlertVariant.success);
@ -60,7 +57,7 @@ export const NewRealmForm = () => {
</PageSection>
<Divider />
<PageSection variant="light">
<Form isHorizontal>
<Form isHorizontal onSubmit={handleSubmit(save)}>
<JsonFileUpload id="kc-realm-filename" onChange={handleFileChange} />
<FormGroup label={t("realmName")} isRequired fieldId="kc-realm-name">
<TextInput
@ -68,22 +65,28 @@ export const NewRealmForm = () => {
type="text"
id="kc-realm-name"
name="realm"
value={realm.realm}
onChange={handleChange}
ref={register()}
/>
</FormGroup>
<FormGroup label={t("enabled")} fieldId="kc-realm-enabled-switch">
<Switch
id="kc-realm-enabled-switch"
<Controller
name="enabled"
label={t("on")}
labelOff={t("off")}
isChecked={realm.enabled}
onChange={handleChange}
defaultValue={true}
control={control}
render={({ onChange, value }) => (
<Switch
id="kc-realm-enabled-switch"
name="enabled"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<ActionGroup>
<Button variant="primary" onClick={() => save()}>
<Button variant="primary" type="submit">
{t("create")}
</Button>
<Button variant="link">{t("common:cancel")}</Button>

View file

@ -3,8 +3,6 @@
"uploadFile":"Upload JSON file",
"realmName":"Realm name",
"enabled":"Enabled",
"on": "On",
"off":"Off",
"create":"Create"
}
}

View file

@ -15700,6 +15700,11 @@ react-helmet-async@^1.0.2:
react-fast-compare "^3.0.1"
shallowequal "^1.1.0"
react-hook-form@^6.8.2:
version "6.8.2"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-6.8.2.tgz#2a63b3d8578bd4f7965767ae4e205c2c24a1c5bc"
integrity sha512-9Xz1uz61fCMtQ0MTTnTIDqUpWWi+livCUsYcIOpUrlJXDHJ7QgDLF3wVKcmLIX727FFyGzesXkVCg8cQuRy8OQ==
react-hotkeys@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0.tgz#a7719c7340cbba888b0e9184f806a9ec0ac2c53f"