Form.tsx is gone, useForm hook is the replacement,
This commit is contained in:
parent
dce95c8345
commit
83cf83b6fc
@ -1,957 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import Joi from "joi";
|
|
||||||
import Input, { InputType } from "./Input";
|
|
||||||
import ToggleSlider from "./ToggleSlider";
|
|
||||||
import Select from "./Select";
|
|
||||||
import Option from "./option";
|
|
||||||
import { GeneralIdRef } from "../../utils/GeneralIdRef";
|
|
||||||
import SequencePicker from "../pickers/SequencePicker";
|
|
||||||
import GlossaryPicker from "../pickers/GlossaryPicker";
|
|
||||||
import CustomFieldsEditor, { CustomFieldEditorAdd, CustomFieldEditorDelete } from "./CustomFieldsEditor";
|
|
||||||
import { CustomField, numberParams, textParams } from "../../modules/manager/customfields/services/customFieldsService";
|
|
||||||
import FormTemplatePicker from "../pickers/FormTemplatePicker";
|
|
||||||
import { CustomFieldValue, CustomFieldValues, Glossary } from "../../modules/manager/glossary/services/glossaryService";
|
|
||||||
import TemplateEditor from "./TemplateEditor";
|
|
||||||
import DomainPicker from "../pickers/DomainPicker";
|
|
||||||
import UserPicker from "../pickers/UserPicker";
|
|
||||||
import Button, { ButtonType } from "./Button";
|
|
||||||
import Expando from "./expando";
|
|
||||||
import ErrorBlock from "./ErrorBlock";
|
|
||||||
import SsoProviderPicker from "../pickers/SsoProviderPicker";
|
|
||||||
|
|
||||||
export interface FormError {
|
|
||||||
[key: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FormData {
|
|
||||||
[key: string]: string | number | boolean | CustomFieldValue[] | GeneralIdRef | CustomField[] | bigint | Glossary | null | undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface businessValidationError {
|
|
||||||
path: string;
|
|
||||||
message: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface businessValidationResult {
|
|
||||||
details: businessValidationError[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface propertyValue {
|
|
||||||
name: string;
|
|
||||||
value: string | boolean | number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface joiSchema {
|
|
||||||
[key: string]: object;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FormState {
|
|
||||||
loaded: boolean;
|
|
||||||
data: FormData;
|
|
||||||
customFields?: CustomField[];
|
|
||||||
errors: FormError;
|
|
||||||
redirect?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Match<P> {
|
|
||||||
params: P;
|
|
||||||
isExact: boolean;
|
|
||||||
path: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface State {
|
|
||||||
from: Location;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LocationProps {
|
|
||||||
hash: string;
|
|
||||||
pathname: string;
|
|
||||||
search: string;
|
|
||||||
state: State;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FormProps<P> {
|
|
||||||
location: LocationProps;
|
|
||||||
match: Match<P>;
|
|
||||||
staticContext?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Form<P, FP extends FormProps<P>, FS extends FormState> extends React.Component<FP, FS> {
|
|
||||||
schema: joiSchema = {};
|
|
||||||
|
|
||||||
validate = (data: FormData) => {
|
|
||||||
let options: Joi.ValidationOptions = {
|
|
||||||
context: {},
|
|
||||||
abortEarly: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { customFields } = this.state;
|
|
||||||
|
|
||||||
let schema = this.schema;
|
|
||||||
if (customFields !== undefined) {
|
|
||||||
for (const customfield of customFields) {
|
|
||||||
const name = "customfield_" + customfield.id;
|
|
||||||
switch (customfield.fieldType) {
|
|
||||||
case "Number":
|
|
||||||
if (customfield.parameters !== undefined) {
|
|
||||||
const parameters: numberParams = JSON.parse(customfield.parameters!);
|
|
||||||
|
|
||||||
options.context![name + "_minEntries"] = customfield.minEntries;
|
|
||||||
if (parameters.minValue) options.context![name + "_minValue"] = Number(parameters.minValue);
|
|
||||||
if (parameters.maxValue) options.context![name + "_maxValue"] = Number(parameters.maxValue);
|
|
||||||
|
|
||||||
let minCheck = options.context![name + "_minValue"]
|
|
||||||
? Joi.number()
|
|
||||||
.empty("")
|
|
||||||
.min(options.context![name + "_minValue"])
|
|
||||||
: Joi.number().empty("");
|
|
||||||
let maxCheck = options.context![name + "_maxValue"]
|
|
||||||
? Joi.number()
|
|
||||||
.empty("")
|
|
||||||
.max(options.context![name + "_maxValue"])
|
|
||||||
: Joi.number().empty("");
|
|
||||||
|
|
||||||
schema[name] = Joi.array()
|
|
||||||
.min(1)
|
|
||||||
.items(
|
|
||||||
Joi.object({
|
|
||||||
displayValue: Joi.string().allow(""),
|
|
||||||
value: Joi.when("$" + name + "_minEntries", {
|
|
||||||
is: 0,
|
|
||||||
then: Joi.number().empty(""),
|
|
||||||
otherwise: Joi.number().required(),
|
|
||||||
})
|
|
||||||
.when("$" + name + "_minValue", {
|
|
||||||
is: Joi.number(),
|
|
||||||
then: minCheck,
|
|
||||||
})
|
|
||||||
.when("$" + name + "_maxValue", {
|
|
||||||
is: Joi.number(),
|
|
||||||
then: maxCheck,
|
|
||||||
})
|
|
||||||
.label(customfield.name),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
schema[name] = Joi.optional().label(customfield.name);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
schema[name] = Joi.optional().label(customfield.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const joiSchema = Joi.object(schema);
|
|
||||||
const { error } = joiSchema.validate(data, options);
|
|
||||||
let errors: FormError = {};
|
|
||||||
if (error) {
|
|
||||||
if (error.details === undefined) {
|
|
||||||
errors[error.name] = error.message;
|
|
||||||
} else {
|
|
||||||
for (let item of error.details) {
|
|
||||||
errors[item.path[0]] = item.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
GetCustomFieldValues = (customField: CustomField) => {
|
|
||||||
const name = "customfield_" + customField.id;
|
|
||||||
|
|
||||||
const { data } = this.state;
|
|
||||||
|
|
||||||
const codedValue = data[name];
|
|
||||||
|
|
||||||
let values: CustomFieldValue[] = [];
|
|
||||||
|
|
||||||
switch (customField.fieldType) {
|
|
||||||
case "FormTemplate":
|
|
||||||
if (codedValue !== undefined) {
|
|
||||||
const formTemplateValue = { value: JSON.stringify(codedValue as GeneralIdRef) };
|
|
||||||
values.push(formTemplateValue);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Sequence":
|
|
||||||
if (codedValue !== undefined) {
|
|
||||||
values = codedValue as CustomFieldValue[];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Glossary":
|
|
||||||
if (codedValue !== undefined) {
|
|
||||||
values = codedValue as CustomFieldValue[];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Domain":
|
|
||||||
if (codedValue !== undefined) {
|
|
||||||
values = codedValue as CustomFieldValue[];
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Text":
|
|
||||||
const textParameters: textParams = JSON.parse(customField.parameters!);
|
|
||||||
if (textParameters.multiLine) {
|
|
||||||
const textValue = {
|
|
||||||
value: codedValue === undefined ? customField.defaultValue : codedValue,
|
|
||||||
displayValue: codedValue === undefined ? customField.defaultValue : codedValue,
|
|
||||||
} as CustomFieldValue;
|
|
||||||
values.push(textValue);
|
|
||||||
} else {
|
|
||||||
if (codedValue === undefined) {
|
|
||||||
const numberValue = {
|
|
||||||
value: customField.defaultValue,
|
|
||||||
displayValue: customField.defaultValue,
|
|
||||||
} as CustomFieldValue;
|
|
||||||
values.push(numberValue);
|
|
||||||
} else {
|
|
||||||
values = codedValue as CustomFieldValue[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "Number":
|
|
||||||
if (codedValue === undefined) {
|
|
||||||
const numberValue = {
|
|
||||||
value: customField.defaultValue,
|
|
||||||
displayValue: customField.defaultValue,
|
|
||||||
} as CustomFieldValue;
|
|
||||||
values.push(numberValue);
|
|
||||||
} else {
|
|
||||||
values = codedValue as CustomFieldValue[];
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
const textValue = { value: codedValue === undefined ? customField.defaultValue : String((codedValue as CustomFieldValue[])[0].displayValue) };
|
|
||||||
values.push(textValue);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return values;
|
|
||||||
};
|
|
||||||
|
|
||||||
CustomFieldValues = () => {
|
|
||||||
const { customFields } = this.state;
|
|
||||||
|
|
||||||
let result: CustomFieldValues[] = [];
|
|
||||||
|
|
||||||
if (customFields === undefined) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const customfield of customFields) {
|
|
||||||
const values = this.GetCustomFieldValues(customfield);
|
|
||||||
|
|
||||||
const id: GeneralIdRef = {
|
|
||||||
id: customfield.id,
|
|
||||||
guid: customfield.guid,
|
|
||||||
};
|
|
||||||
|
|
||||||
const newItem: CustomFieldValues = {
|
|
||||||
id,
|
|
||||||
values,
|
|
||||||
};
|
|
||||||
|
|
||||||
result.push(newItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
setCustomFieldValues(data: object, customFieldValues: CustomFieldValues[], customFields: CustomField[]) {
|
|
||||||
if (customFieldValues !== undefined) {
|
|
||||||
for (const x of customFieldValues) {
|
|
||||||
const customfieldName = "customfield_" + x.id.id;
|
|
||||||
|
|
||||||
switch (this.getCustomFieldType(x, customFields).toLowerCase()) {
|
|
||||||
case "glossary":
|
|
||||||
case "domain":
|
|
||||||
case "number":
|
|
||||||
case "text":
|
|
||||||
(data as any)[customfieldName] = x.values.map((x) => {
|
|
||||||
return {
|
|
||||||
displayValue: x.displayValue,
|
|
||||||
value: x.value,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
case "formtemplate":
|
|
||||||
case "multilinetext":
|
|
||||||
(data as any)[customfieldName] = x.values[0].value;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
(data as any)[customfieldName] = x.values;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getCustomFieldType = (field: CustomFieldValues, childCustomFieldDefinition: CustomField[]): string => {
|
|
||||||
const fieldDefinition = childCustomFieldDefinition.filter((x) => x.id === field.id.id)[0];
|
|
||||||
|
|
||||||
if (fieldDefinition.parameters) {
|
|
||||||
const textParameters: textParams = JSON.parse(fieldDefinition.parameters!);
|
|
||||||
if (textParameters.multiLine) return "multilinetext";
|
|
||||||
}
|
|
||||||
|
|
||||||
return fieldDefinition.fieldType;
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const submitEvent = e.nativeEvent as SubmitEvent;
|
|
||||||
|
|
||||||
const submitter = submitEvent.submitter as any;
|
|
||||||
|
|
||||||
const errors = this.validate(this.state.data);
|
|
||||||
this.setState({ errors: errors });
|
|
||||||
|
|
||||||
const disabled = Object.keys(errors).length > 0;
|
|
||||||
|
|
||||||
if (disabled) return;
|
|
||||||
|
|
||||||
this.doSubmit(submitter.name);
|
|
||||||
};
|
|
||||||
|
|
||||||
doSubmit = async (buttonName: string) => {};
|
|
||||||
|
|
||||||
handleGeneralError = (ex: any) => {
|
|
||||||
const errors: FormError = { ...this.state.errors };
|
|
||||||
|
|
||||||
if (ex.response) {
|
|
||||||
errors._general = ex.response.data.detail;
|
|
||||||
} else {
|
|
||||||
errors._general = ex.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const input = e.currentTarget;
|
|
||||||
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
|
|
||||||
if ((input as any).type === InputType.checkbox) {
|
|
||||||
data[input.name] = !data[input.name];
|
|
||||||
} else data[input.name] = input.value;
|
|
||||||
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleTextAreaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
||||||
const input = e.currentTarget;
|
|
||||||
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
|
|
||||||
data[input.name] = input.value;
|
|
||||||
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleCustomFieldChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const input = e.currentTarget;
|
|
||||||
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
|
|
||||||
switch ((input as any).type) {
|
|
||||||
case InputType.checkbox:
|
|
||||||
data[input.name] = !data[input.name];
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
const customFieldValue: CustomFieldValue = {
|
|
||||||
displayValue: input.value,
|
|
||||||
value: input.value,
|
|
||||||
};
|
|
||||||
|
|
||||||
data[input.name] = [customFieldValue];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleTemplateEditorChange = (name: string, value: string) => {
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
|
|
||||||
data[name] = value;
|
|
||||||
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
|
||||||
const input = e.currentTarget;
|
|
||||||
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
data[input.name] = input.value;
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handlePickerChange = (name: string, value: GeneralIdRef) => {
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
data[name] = value;
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleDomainPickerChange = (name: string, values: CustomFieldValue[]) => {
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
data[name] = values;
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleGlossaryPickerChange = (name: string, values: CustomFieldValue[]) => {
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
data[name] = values;
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleTemplateFormPickerChange = (name: string, value: GeneralIdRef) => {
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
data[name] = value;
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleUserPickerChange = (name: string, value: GeneralIdRef) => {
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
data[name] = value;
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleSsoProviderPickerChange = (name: string, value: GeneralIdRef) => {
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
data[name] = value;
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
handleToggleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const input = e.currentTarget;
|
|
||||||
const { name, checked } = input;
|
|
||||||
|
|
||||||
const data: FormData = { ...this.state.data };
|
|
||||||
data[name] = checked;
|
|
||||||
const errors = this.validate(data);
|
|
||||||
|
|
||||||
this.setState({ data, errors });
|
|
||||||
};
|
|
||||||
|
|
||||||
renderButton(
|
|
||||||
label: string,
|
|
||||||
name?: string,
|
|
||||||
onClick?: (keyValue: any) => {},
|
|
||||||
testid?: string,
|
|
||||||
enabled: boolean = true,
|
|
||||||
buttonType: ButtonType = ButtonType.primary,
|
|
||||||
overrideErrorChecking: boolean = false
|
|
||||||
) {
|
|
||||||
const { errors } = this.state;
|
|
||||||
|
|
||||||
let disabled = !enabled || Object.keys(errors).filter((x) => !x.startsWith("_")).length > 0;
|
|
||||||
|
|
||||||
if (overrideErrorChecking) disabled = !enabled;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button testid={testid} disabled={disabled} name={name ?? label} buttonType={buttonType} onClick={onClick}>
|
|
||||||
{label}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderError(name: string) {
|
|
||||||
const { errors } = this.state;
|
|
||||||
|
|
||||||
return <ErrorBlock error={errors[name]} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderInput(
|
|
||||||
name: string,
|
|
||||||
label: string,
|
|
||||||
type: InputType = InputType.text,
|
|
||||||
readOnly = false,
|
|
||||||
defaultValue: string = "",
|
|
||||||
placeHolder: string = "",
|
|
||||||
maxLength: number = 0,
|
|
||||||
visible: boolean = true,
|
|
||||||
autoComplete: string | undefined = undefined
|
|
||||||
) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
let value = data[name];
|
|
||||||
|
|
||||||
let cleanValue: string | undefined;
|
|
||||||
if (value === undefined) {
|
|
||||||
cleanValue = defaultValue;
|
|
||||||
} else if (typeof value === "string") {
|
|
||||||
cleanValue = value as string;
|
|
||||||
} else if (typeof value === "number") {
|
|
||||||
cleanValue = String(value);
|
|
||||||
} else if (typeof value === "boolean") {
|
|
||||||
cleanValue = String(value);
|
|
||||||
} else if (value as CustomFieldValue) {
|
|
||||||
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readOnly) {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
includeLabel={true}
|
|
||||||
type={type}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={cleanValue}
|
|
||||||
error={errors[name]}
|
|
||||||
maxLength={maxLength}
|
|
||||||
readOnly
|
|
||||||
placeHolder={placeHolder}
|
|
||||||
hidden={!visible}
|
|
||||||
autoComplete={autoComplete}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
includeLabel={true}
|
|
||||||
type={type}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={cleanValue}
|
|
||||||
error={errors[name]}
|
|
||||||
maxLength={maxLength}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
placeHolder={placeHolder}
|
|
||||||
hidden={!visible}
|
|
||||||
autoComplete={autoComplete}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderInputWithChangeEvent(
|
|
||||||
name: string,
|
|
||||||
label: string,
|
|
||||||
type: InputType = InputType.text,
|
|
||||||
readOnly = false,
|
|
||||||
handleChangeEvent: any,
|
|
||||||
defaultValue: string = "",
|
|
||||||
placeHolder: string = "",
|
|
||||||
maxLength: number = 0
|
|
||||||
) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
let value = data[name];
|
|
||||||
|
|
||||||
let cleanValue: string | undefined;
|
|
||||||
if (value === undefined) {
|
|
||||||
cleanValue = defaultValue;
|
|
||||||
} else if (typeof value === "string") {
|
|
||||||
cleanValue = value as string;
|
|
||||||
} else if (typeof value === "number") {
|
|
||||||
cleanValue = String(value);
|
|
||||||
} else if (typeof value === "boolean") {
|
|
||||||
cleanValue = String(value);
|
|
||||||
} else if (value as CustomFieldValue) {
|
|
||||||
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readOnly) {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
includeLabel={true}
|
|
||||||
type={type}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={cleanValue}
|
|
||||||
error={errors[name]}
|
|
||||||
maxLength={maxLength}
|
|
||||||
readOnly
|
|
||||||
placeHolder={placeHolder}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
includeLabel={true}
|
|
||||||
type={type}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={cleanValue}
|
|
||||||
error={errors[name]}
|
|
||||||
maxLength={maxLength}
|
|
||||||
onChange={handleChangeEvent}
|
|
||||||
placeHolder={placeHolder}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderInputNumber(name: string, label: string, readOnly = false, defaultValue: string = "", min?: number, max?: number, step: number = 1) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
let value = data[name];
|
|
||||||
|
|
||||||
let cleanValue: string | undefined;
|
|
||||||
if (value === undefined) {
|
|
||||||
cleanValue = defaultValue;
|
|
||||||
} else if (typeof value === "string") {
|
|
||||||
cleanValue = value as string;
|
|
||||||
} else if (typeof value === "number") {
|
|
||||||
cleanValue = String(value);
|
|
||||||
} else if (typeof value === "boolean") {
|
|
||||||
cleanValue = String(value);
|
|
||||||
} else if (value as CustomFieldValue) {
|
|
||||||
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readOnly) {
|
|
||||||
return <Input includeLabel={true} type={InputType.number} name={name} label={label} value={cleanValue} error={errors[name]} readOnly />;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
includeLabel={true}
|
|
||||||
type={InputType.number}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={cleanValue}
|
|
||||||
error={errors[name]}
|
|
||||||
min={min}
|
|
||||||
max={max}
|
|
||||||
step={step}
|
|
||||||
onChange={this.handleChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderInputTextarea(includeLabel: boolean, name: string, label: string, readOnly = false, defaultValue: string = "") {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
let value = data[name];
|
|
||||||
|
|
||||||
let cleanValue: string | undefined;
|
|
||||||
if (value === undefined) {
|
|
||||||
cleanValue = defaultValue;
|
|
||||||
} else if (typeof value === "string") {
|
|
||||||
cleanValue = value as string;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readOnly) {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
includeLabel={includeLabel}
|
|
||||||
type={InputType.textarea}
|
|
||||||
key={name}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={cleanValue}
|
|
||||||
error={errors[name]}
|
|
||||||
readOnly
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
includeLabel={includeLabel}
|
|
||||||
type={InputType.textarea}
|
|
||||||
key={name}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={cleanValue}
|
|
||||||
error={errors[name]}
|
|
||||||
onTextAreaChange={this.handleTextAreaChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCustomFieldInput(includeLabel: boolean, name: string, label: string, type: InputType = InputType.text, readOnly = false, defaultValue: string = "") {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
let value = data[name];
|
|
||||||
|
|
||||||
let cleanValue: string | undefined;
|
|
||||||
if (value === undefined) {
|
|
||||||
cleanValue = defaultValue;
|
|
||||||
} else if (typeof value === "string") {
|
|
||||||
cleanValue = value as string;
|
|
||||||
} else if (typeof value === "number") {
|
|
||||||
cleanValue = String(value);
|
|
||||||
} else if (typeof value === "boolean") {
|
|
||||||
cleanValue = String(value);
|
|
||||||
} else if (value as CustomFieldValue) {
|
|
||||||
const customFieldValue = value as CustomFieldValue[];
|
|
||||||
cleanValue = customFieldValue[0].displayValue ?? customFieldValue[0].value?.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readOnly) {
|
|
||||||
return <Input includeLabel={includeLabel} type={type} name={name} label={label} value={cleanValue} error={errors[name]} readOnly />;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
includeLabel={includeLabel}
|
|
||||||
type={type}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={cleanValue}
|
|
||||||
error={errors[name]}
|
|
||||||
onChange={this.handleCustomFieldChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCustomFieldNumber(
|
|
||||||
includeLabel: boolean,
|
|
||||||
name: string,
|
|
||||||
label: string,
|
|
||||||
readOnly = false,
|
|
||||||
defaultValue: string = "",
|
|
||||||
min?: number,
|
|
||||||
max?: number,
|
|
||||||
step: number = 1
|
|
||||||
) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
let values: CustomFieldValue[] = data[name] as CustomFieldValue[];
|
|
||||||
|
|
||||||
let value: CustomFieldValue | undefined = undefined;
|
|
||||||
|
|
||||||
if (values) {
|
|
||||||
if (values.length > 0) {
|
|
||||||
value = values[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let cleanValue: string | undefined;
|
|
||||||
if (value === undefined) {
|
|
||||||
cleanValue = defaultValue;
|
|
||||||
} else if (typeof value === "string") {
|
|
||||||
cleanValue = value as string;
|
|
||||||
} else if (typeof value === "number") {
|
|
||||||
cleanValue = String(value);
|
|
||||||
} else if (value as CustomFieldValue) {
|
|
||||||
cleanValue = (value as CustomFieldValue).displayValue ?? (value as CustomFieldValue).value?.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (readOnly) {
|
|
||||||
return <Input includeLabel={includeLabel} type={InputType.number} name={name} label={label} value={cleanValue} error={errors[name]} readOnly />;
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<Input
|
|
||||||
includeLabel={includeLabel}
|
|
||||||
type={InputType.number}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={cleanValue}
|
|
||||||
error={errors[name]}
|
|
||||||
min={min}
|
|
||||||
max={max}
|
|
||||||
step={step}
|
|
||||||
onChange={this.handleCustomFieldChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTemplateEditor(className: string, name: string, label: string, allowCustomFields: boolean) {
|
|
||||||
const { data } = this.state;
|
|
||||||
|
|
||||||
let value = data[name] as string;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<label htmlFor={name}>{label}</label>
|
|
||||||
<TemplateEditor className={className} name={name} data={value} onChange={this.handleTemplateEditorChange} showFields={allowCustomFields} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSelect(name: string, label: string, options: Option[]) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
return <Select name={name} label={label} value={data[name]} options={options} error={errors[name]} onChange={this.handleSelectChange} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderToggle(name: string, label: string) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
return <ToggleSlider name={name} label={label} defaultChecked={Boolean(data[name])} error={errors[name]} onChange={this.handleToggleChange} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSequencePicker(includeLabel: boolean, name: string, label: string) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SequencePicker includeLabel={includeLabel} name={name} label={label} value={data[name]} error={errors[name]} onChange={this.handlePickerChange} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderGlossaryPicker(includeLabel: boolean, name: string, label: string, maxEntries?: number, refElementId?: GeneralIdRef) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
const glossaryValues: CustomFieldValue[] | undefined = data[name] as any as CustomFieldValue[];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<GlossaryPicker
|
|
||||||
includeLabel={includeLabel}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
maxEntries={maxEntries}
|
|
||||||
values={glossaryValues}
|
|
||||||
error={errors[name]}
|
|
||||||
rootItem={refElementId}
|
|
||||||
onChange={this.handleGlossaryPickerChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDomainPicker(includeLabel: boolean, name: string, label: string, minEntries: number, maxEntries?: number) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
const domainValues: CustomFieldValue[] | undefined = data[name] as any as CustomFieldValue[];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DomainPicker
|
|
||||||
includeLabel={includeLabel}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
minEntries={minEntries}
|
|
||||||
maxEntries={maxEntries}
|
|
||||||
values={domainValues}
|
|
||||||
error={errors[name]}
|
|
||||||
onChange={this.handleDomainPickerChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTemplatePicker(includeLabel: boolean, name: string, label: string) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
const templateValue: GeneralIdRef = data[name] as any as GeneralIdRef;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormTemplatePicker
|
|
||||||
includeLabel={includeLabel}
|
|
||||||
name={name}
|
|
||||||
label={label}
|
|
||||||
value={templateValue}
|
|
||||||
error={errors[name]}
|
|
||||||
onChange={this.handleTemplateFormPickerChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderUserPicker(name: string, label: string) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
const glossaryValue: GeneralIdRef | undefined = data[name] as any as GeneralIdRef;
|
|
||||||
|
|
||||||
return <UserPicker name={name} label={label} value={glossaryValue} error={errors[name]} onChange={this.handleUserPickerChange} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderSsoProviderPicker(name: string, label: string) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
const glossaryValue: GeneralIdRef | undefined = data[name] as any as GeneralIdRef;
|
|
||||||
|
|
||||||
return <SsoProviderPicker name={name} label={label} value={glossaryValue} error={errors[name]} onChange={this.handleSsoProviderPickerChange} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCustomFieldsEditor(name: string, label: string, selected: CustomField[], onAdd: CustomFieldEditorAdd, onDelete: CustomFieldEditorDelete) {
|
|
||||||
const { data, errors } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CustomFieldsEditor name={name} label={label} value={data[name] as []} error={errors[name]} exclude={selected} onAdd={onAdd} onDelete={onDelete} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCustomFields(customFields: CustomField[] | undefined) {
|
|
||||||
if (customFields === undefined) return <></>;
|
|
||||||
|
|
||||||
let customFieldsBlock: JSX.Element[] = [];
|
|
||||||
for (const customField of customFields) customFieldsBlock.push(this.renderCustomField(customField, true));
|
|
||||||
|
|
||||||
return <>{customFieldsBlock.map((x) => x)}</>;
|
|
||||||
}
|
|
||||||
|
|
||||||
renderCustomField(customField: CustomField, includeLabel: boolean) {
|
|
||||||
switch (customField.fieldType.toLowerCase()) {
|
|
||||||
case "text":
|
|
||||||
const textParameters: textParams = JSON.parse(customField.parameters!);
|
|
||||||
if (textParameters.multiLine) {
|
|
||||||
return this.renderInputTextarea(includeLabel, "customfield_" + customField.id, customField.name, false, customField.defaultValue);
|
|
||||||
} else {
|
|
||||||
return this.renderCustomFieldInput(
|
|
||||||
includeLabel,
|
|
||||||
"customfield_" + customField.id,
|
|
||||||
customField.name,
|
|
||||||
InputType.text,
|
|
||||||
false,
|
|
||||||
customField.defaultValue
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "sequence":
|
|
||||||
return this.renderCustomFieldInput(includeLabel, "customfield_" + customField.id, customField.name, InputType.text, true);
|
|
||||||
case "formtemplate":
|
|
||||||
return this.renderTemplatePicker(includeLabel, "customfield_" + customField.id, customField.name);
|
|
||||||
case "glossary":
|
|
||||||
return this.renderGlossaryPicker(
|
|
||||||
includeLabel,
|
|
||||||
"customfield_" + customField.id,
|
|
||||||
customField.name,
|
|
||||||
customField.maxEntries,
|
|
||||||
customField.refElementId
|
|
||||||
);
|
|
||||||
case "number":
|
|
||||||
const numberParameters: numberParams = JSON.parse(customField.parameters!);
|
|
||||||
return this.renderCustomFieldNumber(
|
|
||||||
includeLabel,
|
|
||||||
"customfield_" + customField.id,
|
|
||||||
customField.name,
|
|
||||||
false,
|
|
||||||
customField.defaultValue,
|
|
||||||
numberParameters.minValue ? Number(numberParameters.minValue) : undefined,
|
|
||||||
numberParameters.maxValue ? Number(numberParameters.maxValue) : undefined,
|
|
||||||
numberParameters.step ? Number(numberParameters.step) : undefined
|
|
||||||
);
|
|
||||||
case "domain":
|
|
||||||
return this.renderDomainPicker(includeLabel, "customfield_" + customField.id, customField.name, customField.minEntries, customField.maxEntries);
|
|
||||||
default:
|
|
||||||
return <>{customField.name + " " + customField.fieldType}</>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderDropSection(name: string, title: JSX.Element, content: JSX.Element) {
|
|
||||||
const { errors } = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Expando name={name} title={title} error={errors[name]}>
|
|
||||||
{content}
|
|
||||||
</Expando>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Form;
|
|
||||||
@ -1,4 +1,10 @@
|
|||||||
import React from "react";
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useImperativeHandle,
|
||||||
|
useRef,
|
||||||
|
forwardRef,
|
||||||
|
} from "react";
|
||||||
import { GeneralIdRef, MakeGeneralIdRef } from "../../utils/GeneralIdRef";
|
import { GeneralIdRef, MakeGeneralIdRef } from "../../utils/GeneralIdRef";
|
||||||
import formsService, {
|
import formsService, {
|
||||||
CreateFormInstance,
|
CreateFormInstance,
|
||||||
@ -6,194 +12,246 @@ import formsService, {
|
|||||||
} from "../../modules/manager/forms/services/formsService";
|
} from "../../modules/manager/forms/services/formsService";
|
||||||
import parse, { HTMLReactParserOptions, domToReact } from "html-react-parser";
|
import parse, { HTMLReactParserOptions, domToReact } from "html-react-parser";
|
||||||
import { CustomField } from "../../modules/manager/customfields/services/customFieldsService";
|
import { CustomField } from "../../modules/manager/customfields/services/customFieldsService";
|
||||||
import Form, { FormState } from "./Form";
|
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import Loading from "./Loading";
|
import Loading from "./Loading";
|
||||||
|
import { useForm } from "./useForm";
|
||||||
|
import { renderCustomField } from "./formHelpers";
|
||||||
|
import { CustomFieldValue } from "../../modules/manager/glossary/services/glossaryService";
|
||||||
|
|
||||||
interface TemplateFillerProps {
|
interface TemplateFillerProps {
|
||||||
templateId?: GeneralIdRef;
|
templateId?: GeneralIdRef;
|
||||||
formInstanceId?: GeneralIdRef;
|
formInstanceId?: GeneralIdRef;
|
||||||
onValidationChanged?: () => {};
|
onValidationChanged?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TemplateFillerState extends FormState {
|
export interface TemplateFillerHandle {
|
||||||
customFields?: CustomField[];
|
hasValidationErrors: () => boolean;
|
||||||
template: {
|
Save: () => Promise<any>;
|
||||||
name?: string;
|
|
||||||
templateId?: GeneralIdRef;
|
|
||||||
version?: bigint;
|
|
||||||
definition?: string;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class TemplateFiller extends Form<
|
interface TemplateState {
|
||||||
TemplateFillerProps,
|
name?: string;
|
||||||
any,
|
templateId?: GeneralIdRef;
|
||||||
TemplateFillerState
|
version?: bigint;
|
||||||
> {
|
definition?: string;
|
||||||
state: TemplateFillerState = {
|
}
|
||||||
loaded: false,
|
|
||||||
customFields: undefined,
|
const TemplateFiller = forwardRef<TemplateFillerHandle, TemplateFillerProps>(
|
||||||
template: {
|
({ templateId, formInstanceId, onValidationChanged }, ref) => {
|
||||||
|
const form = useForm({
|
||||||
|
loaded: false,
|
||||||
|
data: {},
|
||||||
|
errors: {},
|
||||||
|
redirect: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const [template, setTemplate] = React.useState<TemplateState>({
|
||||||
name: undefined,
|
name: undefined,
|
||||||
templateId: undefined,
|
templateId: undefined,
|
||||||
version: undefined,
|
version: undefined,
|
||||||
definition: undefined,
|
definition: undefined,
|
||||||
},
|
});
|
||||||
data: {},
|
|
||||||
errors: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
schema = {};
|
const prevHasErrorsRef = useRef<boolean>(false);
|
||||||
|
|
||||||
componentDidMount(): void {
|
form.schema = {};
|
||||||
this.loadTemplate();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(
|
const loadTemplate = useCallback(async () => {
|
||||||
prevProps: Readonly<TemplateFillerProps>,
|
let loadedData: any;
|
||||||
prevState: Readonly<TemplateFillerState>,
|
|
||||||
snapshot?: any,
|
|
||||||
): void {
|
|
||||||
if (
|
|
||||||
prevProps.formInstanceId !== this.props.formInstanceId ||
|
|
||||||
prevProps.templateId !== this.props.templateId
|
|
||||||
)
|
|
||||||
this.loadTemplate();
|
|
||||||
|
|
||||||
if (this.props.onValidationChanged) {
|
if (templateId !== undefined) {
|
||||||
const prevErrorCount = Object.keys(prevState.errors).length > 0;
|
loadedData = await formsService.getForm(
|
||||||
const errorCount = Object.keys(this.state.errors).length > 0;
|
templateId?.id,
|
||||||
|
templateId?.guid,
|
||||||
if (prevErrorCount !== errorCount) {
|
);
|
||||||
this.props.onValidationChanged();
|
loadedData.templateId = undefined;
|
||||||
|
loadedData.customFieldValues = undefined;
|
||||||
|
loadedData.updatedVersion = undefined;
|
||||||
|
} else if (formInstanceId !== undefined) {
|
||||||
|
loadedData = await formsService.getFormInstance(
|
||||||
|
formInstanceId?.id,
|
||||||
|
formInstanceId?.guid,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
loadedData = {
|
||||||
|
name: undefined,
|
||||||
|
id: undefined,
|
||||||
|
guid: undefined,
|
||||||
|
version: undefined,
|
||||||
|
definition: undefined,
|
||||||
|
customFieldDefinitions: undefined,
|
||||||
|
templateId: undefined,
|
||||||
|
customFieldValues: undefined,
|
||||||
|
updatedVersion: undefined,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadTemplate = async () => {
|
const newTemplate: TemplateState = {
|
||||||
const { templateId, formInstanceId } = this.props;
|
name: loadedData.name,
|
||||||
let loadedData: any;
|
templateId: MakeGeneralIdRef(loadedData.id, loadedData.guid),
|
||||||
|
version: loadedData.version,
|
||||||
if (templateId !== undefined) {
|
definition: loadedData.definition,
|
||||||
loadedData = await formsService.getForm(templateId?.id, templateId?.guid);
|
|
||||||
//Get the form definiton for the template provided by templateId and load.
|
|
||||||
|
|
||||||
loadedData.templateId = undefined;
|
|
||||||
loadedData.customFieldValues = undefined;
|
|
||||||
loadedData.updatedVersion = undefined;
|
|
||||||
} else if (formInstanceId !== undefined) {
|
|
||||||
loadedData = await formsService.getFormInstance(
|
|
||||||
formInstanceId?.id,
|
|
||||||
formInstanceId?.guid,
|
|
||||||
);
|
|
||||||
console.log("formInstanceId", loadedData);
|
|
||||||
} else {
|
|
||||||
loadedData = {
|
|
||||||
name: undefined,
|
|
||||||
id: undefined,
|
|
||||||
guid: undefined,
|
|
||||||
version: undefined,
|
|
||||||
definition: undefined,
|
|
||||||
customFieldDefinitions: undefined,
|
|
||||||
templateId: undefined,
|
|
||||||
customFieldValues: undefined,
|
|
||||||
updatedVersion: undefined,
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
const { template, data } = this.state;
|
const newCustomFields = loadedData.customFieldDefinitions as
|
||||||
|
| CustomField[]
|
||||||
|
| undefined;
|
||||||
|
const newData = { ...form.state.data } as Record<string, any>;
|
||||||
|
|
||||||
template.name = loadedData.name;
|
form.setCustomFieldValues(
|
||||||
template.templateId = MakeGeneralIdRef(loadedData.id, loadedData.guid);
|
newData,
|
||||||
template.version = loadedData.version;
|
loadedData.customFieldValues,
|
||||||
template.definition = loadedData.definition;
|
newCustomFields ?? [],
|
||||||
const customFields = loadedData.customFieldDefinitions;
|
);
|
||||||
|
|
||||||
this.setCustomFieldValues(data, loadedData.customFieldValues, customFields);
|
form.setState({
|
||||||
|
loaded: true,
|
||||||
|
data: newData,
|
||||||
|
customFields: newCustomFields,
|
||||||
|
});
|
||||||
|
setTemplate(newTemplate);
|
||||||
|
}, [templateId, formInstanceId, form]);
|
||||||
|
|
||||||
this.setState({ loaded: true, template, customFields, data });
|
useEffect(() => {
|
||||||
};
|
void loadTemplate();
|
||||||
|
}, [loadTemplate]);
|
||||||
|
|
||||||
parseDefinition = (
|
useEffect(() => {
|
||||||
definition: string,
|
if (!onValidationChanged) return;
|
||||||
customFieldDefinitions: CustomField[],
|
|
||||||
) => {
|
const hasErrors = Object.keys(form.state.errors).length > 0;
|
||||||
const options: HTMLReactParserOptions = {
|
if (prevHasErrorsRef.current !== hasErrors) {
|
||||||
replace: (domNode) => {
|
prevHasErrorsRef.current = hasErrors;
|
||||||
const domNodeAsAny: any = domNode;
|
onValidationChanged();
|
||||||
if (domNodeAsAny.name === "span") {
|
}
|
||||||
if (domNodeAsAny.attribs.fieldtype === "CustomField") {
|
}, [form.state.errors, onValidationChanged]);
|
||||||
const customField = customFieldDefinitions.filter(
|
|
||||||
(x) => x.guid === domNodeAsAny.attribs.guid,
|
const handleCustomFieldChange = useCallback(
|
||||||
)[0];
|
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
|
||||||
return this.renderCustomField(customField, false);
|
if (e.target instanceof HTMLTextAreaElement) {
|
||||||
}
|
form.handleTextAreaChange(
|
||||||
} else if (domNodeAsAny.name === "p") {
|
e as React.ChangeEvent<HTMLTextAreaElement>,
|
||||||
return (
|
);
|
||||||
<div className="p">
|
} else {
|
||||||
{domToReact(domNodeAsAny.children, options)}
|
form.handleCustomFieldChange(
|
||||||
</div>
|
e as React.ChangeEvent<HTMLInputElement>,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
[form],
|
||||||
|
);
|
||||||
|
|
||||||
return parse(definition, options);
|
const handleCustomFieldPickerChange = useCallback(
|
||||||
};
|
(name: string, value: GeneralIdRef | CustomFieldValue[]) => {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
form.handleGlossaryPickerChange(name, value);
|
||||||
|
} else {
|
||||||
|
form.handlePickerChange(name, value as GeneralIdRef);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[form],
|
||||||
|
);
|
||||||
|
|
||||||
hasValidationErrors = (): boolean => {
|
const getCustomFieldType = useCallback(
|
||||||
const { errors } = this.state;
|
(field: CustomFieldValue, customFields: CustomField[]) => {
|
||||||
|
return form.getCustomFieldType(field as any, customFields);
|
||||||
|
},
|
||||||
|
[form],
|
||||||
|
);
|
||||||
|
|
||||||
const result = Object.keys(errors).length > 0;
|
const parseDefinition = useCallback(
|
||||||
return result;
|
(definition: string, customFieldDefinitions: CustomField[]) => {
|
||||||
};
|
const options: HTMLReactParserOptions = {
|
||||||
|
replace: (domNode) => {
|
||||||
|
const domNodeAsAny: any = domNode;
|
||||||
|
if (domNodeAsAny.name === "span") {
|
||||||
|
if (domNodeAsAny.attribs.fieldtype === "CustomField") {
|
||||||
|
const customField = customFieldDefinitions.filter(
|
||||||
|
(x) => x.guid === domNodeAsAny.attribs.guid,
|
||||||
|
)[0];
|
||||||
|
return renderCustomField(
|
||||||
|
customField,
|
||||||
|
false,
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
handleCustomFieldChange,
|
||||||
|
handleCustomFieldPickerChange,
|
||||||
|
getCustomFieldType,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (domNodeAsAny.name === "p") {
|
||||||
|
return (
|
||||||
|
<div className="p">
|
||||||
|
{domToReact(domNodeAsAny.children, options)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
async Save() {
|
return parse(definition, options);
|
||||||
const { errors } = this.state;
|
},
|
||||||
const { templateId, version } = this.state.template;
|
[
|
||||||
const { formInstanceId } = this.props;
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
handleCustomFieldChange,
|
||||||
|
handleCustomFieldPickerChange,
|
||||||
|
getCustomFieldType,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
if (Object.keys(errors).length > 0) {
|
const hasValidationErrors = useCallback((): boolean => {
|
||||||
toast.error("There are errors on the form");
|
return Object.keys(form.state.errors).length > 0;
|
||||||
throw new Error("There are errors on the form");
|
}, [form.state.errors]);
|
||||||
}
|
|
||||||
|
|
||||||
const customFieldValues = this.CustomFieldValues();
|
const Save = useCallback(async () => {
|
||||||
if (formInstanceId !== undefined) {
|
const { errors } = form.state;
|
||||||
if (templateId === undefined) throw Error("TemplateId cannot be null");
|
|
||||||
|
|
||||||
if (version === undefined) throw Error("Version cannot be null");
|
if (Object.keys(errors).length > 0) {
|
||||||
|
toast.error("There are errors on the form");
|
||||||
|
throw new Error("There are errors on the form");
|
||||||
|
}
|
||||||
|
|
||||||
const editFormInstance: EditFormInstance = {
|
const customFieldValues = form.CustomFieldValues();
|
||||||
formInstanceId,
|
if (formInstanceId !== undefined) {
|
||||||
templateId,
|
if (template.templateId === undefined)
|
||||||
version,
|
throw Error("TemplateId cannot be null");
|
||||||
customFieldValues,
|
if (template.version === undefined)
|
||||||
};
|
throw Error("Version cannot be null");
|
||||||
await formsService.putFormInstance(editFormInstance);
|
|
||||||
} else {
|
const editFormInstance: EditFormInstance = {
|
||||||
if (templateId !== undefined && version !== undefined) {
|
formInstanceId,
|
||||||
//const customFieldValues = this.CustomFieldValues();
|
templateId: template.templateId,
|
||||||
const formInstance: CreateFormInstance = {
|
version: template.version,
|
||||||
templateId,
|
|
||||||
version,
|
|
||||||
customFieldValues,
|
customFieldValues,
|
||||||
};
|
};
|
||||||
return await formsService.postFormInstance(formInstance);
|
await formsService.putFormInstance(editFormInstance);
|
||||||
} else throw new Error("template unknown");
|
} else {
|
||||||
}
|
if (
|
||||||
}
|
template.templateId !== undefined &&
|
||||||
|
template.version !== undefined
|
||||||
|
) {
|
||||||
|
const formInstance: CreateFormInstance = {
|
||||||
|
templateId: template.templateId,
|
||||||
|
version: template.version,
|
||||||
|
customFieldValues,
|
||||||
|
};
|
||||||
|
return await formsService.postFormInstance(formInstance);
|
||||||
|
}
|
||||||
|
throw new Error("template unknown");
|
||||||
|
}
|
||||||
|
}, [form, template, formInstanceId]);
|
||||||
|
|
||||||
render() {
|
useImperativeHandle(ref, () => ({
|
||||||
const { loaded, template, customFields } = this.state;
|
hasValidationErrors,
|
||||||
|
Save,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const { loaded, customFields } = form.state;
|
||||||
|
|
||||||
let parsedDefinition: any;
|
let parsedDefinition: any;
|
||||||
if (template.definition)
|
if (template.definition && customFields)
|
||||||
parsedDefinition = this.parseDefinition(
|
parsedDefinition = parseDefinition(template.definition, customFields);
|
||||||
template.definition,
|
|
||||||
customFields!,
|
|
||||||
);
|
|
||||||
else parsedDefinition = <></>;
|
else parsedDefinition = <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -201,7 +259,9 @@ class TemplateFiller extends Form<
|
|||||||
<div className="ck-content form-editor">{parsedDefinition}</div>
|
<div className="ck-content form-editor">{parsedDefinition}</div>
|
||||||
</Loading>
|
</Loading>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
}
|
);
|
||||||
|
|
||||||
|
TemplateFiller.displayName = "TemplateFiller";
|
||||||
|
|
||||||
export default TemplateFiller;
|
export default TemplateFiller;
|
||||||
|
|||||||
@ -1,33 +1,23 @@
|
|||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { Link, Navigate } from "react-router-dom";
|
import { Link, Navigate } from "react-router-dom";
|
||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
import Form, { FormState, FormData } from "../../../components/common/Form";
|
|
||||||
import authentication from "../services/authenticationService";
|
import authentication from "../services/authenticationService";
|
||||||
import { InputType } from "../../../components/common/Input";
|
import { InputType } from "../../../components/common/Input";
|
||||||
import { ButtonType } from "../../../components/common/Button";
|
import { ButtonType } from "../../../components/common/Button";
|
||||||
|
import { useForm } from "../../../components/common/useForm";
|
||||||
|
import {
|
||||||
|
renderButton,
|
||||||
|
renderError,
|
||||||
|
renderInput,
|
||||||
|
} from "../../../components/common/formHelpers";
|
||||||
|
|
||||||
//import '../../../Sass/login.scss';
|
const LoginForm: React.FC = () => {
|
||||||
|
const [isInNextStage, setIsInNextStage] = useState(false);
|
||||||
|
const [emailSent, setEmailSent] = useState(false);
|
||||||
|
const passwordMaxLength = 255;
|
||||||
|
|
||||||
export interface LoginFormStateData extends FormData {
|
const form = useForm({
|
||||||
username: string;
|
|
||||||
password: string;
|
|
||||||
tfaNeeded: boolean;
|
|
||||||
requestTfaRemoval: boolean;
|
|
||||||
securityCode: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoginFormState extends FormState {
|
|
||||||
passwordMaxLength: number;
|
|
||||||
isInNextStage: boolean;
|
|
||||||
emailSent: boolean;
|
|
||||||
data: LoginFormStateData;
|
|
||||||
}
|
|
||||||
|
|
||||||
class LoginForm extends Form<any, any, LoginFormState> {
|
|
||||||
state = {
|
|
||||||
loaded: true,
|
loaded: true,
|
||||||
passwordMaxLength: 255,
|
|
||||||
isInNextStage: false,
|
|
||||||
emailSent: false,
|
|
||||||
data: {
|
data: {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
@ -36,9 +26,10 @@ class LoginForm extends Form<any, any, LoginFormState> {
|
|||||||
securityCode: "",
|
securityCode: "",
|
||||||
},
|
},
|
||||||
errors: {},
|
errors: {},
|
||||||
};
|
redirect: "",
|
||||||
|
});
|
||||||
|
|
||||||
schema = {
|
const schema = {
|
||||||
username: Joi.string()
|
username: Joi.string()
|
||||||
.required()
|
.required()
|
||||||
.email({ tlds: { allow: false } })
|
.email({ tlds: { allow: false } })
|
||||||
@ -49,182 +40,203 @@ class LoginForm extends Form<any, any, LoginFormState> {
|
|||||||
securityCode: Joi.string().allow("").label("Authenticate"),
|
securityCode: Joi.string().allow("").label("Authenticate"),
|
||||||
};
|
};
|
||||||
|
|
||||||
doSubmit = async (buttonName: string) => {
|
form.schema = schema;
|
||||||
const { data } = this.state;
|
|
||||||
await this.performLogin(data);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleNextClick = async (event: React.MouseEvent) => {
|
useEffect(() => {
|
||||||
const data: LoginFormStateData = { ...this.state.data };
|
|
||||||
var validationResult = this.schema.username.validate(data.username);
|
|
||||||
if (validationResult.error === undefined) {
|
|
||||||
const stateData = this.state;
|
|
||||||
stateData.isInNextStage = true;
|
|
||||||
this.setState(stateData);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
handleForgetPassword = async () => {
|
|
||||||
try {
|
|
||||||
const stateData = this.state;
|
|
||||||
await authentication.forgotPassword(stateData.data.username);
|
|
||||||
stateData.emailSent = true;
|
|
||||||
stateData.data.username = "";
|
|
||||||
stateData.data.password = "";
|
|
||||||
this.setState(stateData);
|
|
||||||
} catch (ex: any) {
|
|
||||||
this.handleGeneralError(ex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
authenticationWorkAround = async () => {
|
|
||||||
const data: LoginFormStateData = { ...this.state.data };
|
|
||||||
data.requestTfaRemoval = true;
|
|
||||||
|
|
||||||
await this.performLogin(data);
|
|
||||||
|
|
||||||
this.setState({ data });
|
|
||||||
};
|
|
||||||
|
|
||||||
private async performLogin(data: LoginFormStateData) {
|
|
||||||
try {
|
|
||||||
let result = await authentication.login(
|
|
||||||
data.username,
|
|
||||||
data.password,
|
|
||||||
data.securityCode,
|
|
||||||
data.requestTfaRemoval,
|
|
||||||
);
|
|
||||||
|
|
||||||
switch (result) {
|
|
||||||
case 1: //requires tfa
|
|
||||||
const { data } = this.state;
|
|
||||||
|
|
||||||
if (data.tfaNeeded === true) {
|
|
||||||
//TFA removal Request accepted.
|
|
||||||
} else {
|
|
||||||
data.tfaNeeded = true;
|
|
||||||
|
|
||||||
this.setState({ data });
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2: //logged in
|
|
||||||
window.location.href = "/";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break; //treat at though not logged in.
|
|
||||||
}
|
|
||||||
} catch (ex: any) {
|
|
||||||
this.handleGeneralError(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
window.location.replace("/login");
|
window.location.replace("/login");
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { tfaNeeded, requestTfaRemoval } = this.state.data;
|
const performLogin = useCallback(
|
||||||
const { isInNextStage, data, emailSent, passwordMaxLength } = this.state;
|
async (data: any) => {
|
||||||
const result = this.schema.username.validate(data.username);
|
try {
|
||||||
const validEmail = result.error === undefined ? true : false;
|
const result = await authentication.login(
|
||||||
|
data.username,
|
||||||
|
data.password,
|
||||||
|
data.securityCode,
|
||||||
|
data.requestTfaRemoval,
|
||||||
|
);
|
||||||
|
|
||||||
if (authentication.getCurrentUser()) return <Navigate to="/" />;
|
switch (result) {
|
||||||
|
case 1: {
|
||||||
|
const nextData = { ...form.state.data };
|
||||||
|
|
||||||
const requestTfaRemovalPanel = (
|
if (!nextData.tfaNeeded) {
|
||||||
<div>
|
nextData.tfaNeeded = true;
|
||||||
An email has been sent to you so that you can regain control of your
|
form.setState({ data: nextData });
|
||||||
account.
|
}
|
||||||
</div>
|
break;
|
||||||
);
|
}
|
||||||
|
case 2:
|
||||||
|
window.location.href = "/";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (ex: any) {
|
||||||
|
form.handleGeneralError(ex);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[form],
|
||||||
|
);
|
||||||
|
|
||||||
const loginPanel = (
|
const doSubmit = async () => {
|
||||||
<>
|
const { data } = form.state;
|
||||||
<form onSubmit={this.handleSubmit}>
|
await performLogin(data);
|
||||||
{this.renderInput(
|
};
|
||||||
"username",
|
|
||||||
"",
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
InputType.text,
|
form.handleSubmit(e, async () => doSubmit());
|
||||||
isInNextStage,
|
};
|
||||||
undefined,
|
|
||||||
"Email",
|
const handleNextClick = async () => {
|
||||||
undefined,
|
const data = { ...form.state.data };
|
||||||
undefined,
|
const validationResult = schema.username.validate(data.username);
|
||||||
"username",
|
if (validationResult.error === undefined) {
|
||||||
|
setIsInNextStage(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleForgetPassword = async () => {
|
||||||
|
try {
|
||||||
|
await authentication.forgotPassword(form.state.data.username as string);
|
||||||
|
setEmailSent(true);
|
||||||
|
const nextData = { ...form.state.data, username: "", password: "" };
|
||||||
|
form.setState({ data: nextData });
|
||||||
|
} catch (ex: any) {
|
||||||
|
form.handleGeneralError(ex);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const authenticationWorkAround = async () => {
|
||||||
|
const data = { ...form.state.data, requestTfaRemoval: true };
|
||||||
|
await performLogin(data);
|
||||||
|
form.setState({ data });
|
||||||
|
};
|
||||||
|
|
||||||
|
const { tfaNeeded, requestTfaRemoval } = form.state.data as any;
|
||||||
|
const result = schema.username.validate(form.state.data.username);
|
||||||
|
const validEmail = result.error === undefined;
|
||||||
|
|
||||||
|
if (authentication.getCurrentUser()) return <Navigate to="/" />;
|
||||||
|
|
||||||
|
const requestTfaRemovalPanel = (
|
||||||
|
<div>
|
||||||
|
An email has been sent to you so that you can regain control of your
|
||||||
|
account.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
const loginPanel = (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{renderInput(
|
||||||
|
"username",
|
||||||
|
"",
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
InputType.text,
|
||||||
|
isInNextStage,
|
||||||
|
"",
|
||||||
|
"Email",
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
"username",
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
{renderInput(
|
||||||
|
"password",
|
||||||
|
"",
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
InputType.password,
|
||||||
|
emailSent,
|
||||||
|
"",
|
||||||
|
"Password",
|
||||||
|
passwordMaxLength,
|
||||||
|
isInNextStage,
|
||||||
|
"current-password",
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
{!isInNextStage &&
|
||||||
|
renderButton(
|
||||||
|
"Next",
|
||||||
|
form.state.errors,
|
||||||
|
"next",
|
||||||
|
handleNextClick,
|
||||||
|
"login",
|
||||||
|
validEmail,
|
||||||
|
ButtonType.primary,
|
||||||
|
true,
|
||||||
)}
|
)}
|
||||||
{this.renderInput(
|
|
||||||
"password",
|
|
||||||
"",
|
|
||||||
InputType.password,
|
|
||||||
emailSent,
|
|
||||||
undefined,
|
|
||||||
"Password",
|
|
||||||
passwordMaxLength,
|
|
||||||
isInNextStage,
|
|
||||||
"current-password",
|
|
||||||
)}
|
|
||||||
{!isInNextStage &&
|
|
||||||
this.renderButton(
|
|
||||||
"Next",
|
|
||||||
"login",
|
|
||||||
this.handleNextClick,
|
|
||||||
"next",
|
|
||||||
validEmail,
|
|
||||||
ButtonType.primary,
|
|
||||||
true,
|
|
||||||
)}
|
|
||||||
{isInNextStage && (
|
|
||||||
<div className="clickables">
|
|
||||||
{this.renderButton(
|
|
||||||
"Login",
|
|
||||||
"login",
|
|
||||||
undefined,
|
|
||||||
"login",
|
|
||||||
!emailSent,
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
{isInNextStage && (
|
{isInNextStage && (
|
||||||
<div className="forgottenLink">
|
<div className="clickables">
|
||||||
{this.renderButton(
|
{renderButton(
|
||||||
"Forgotten Password",
|
"Login",
|
||||||
"forgot-password",
|
form.state.errors,
|
||||||
this.handleForgetPassword,
|
"login",
|
||||||
"forgot-password",
|
undefined,
|
||||||
validEmail,
|
"login",
|
||||||
ButtonType.secondary,
|
!emailSent,
|
||||||
true,
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{emailSent && (
|
|
||||||
<div className="alert alert-info emailSent">
|
|
||||||
If you have a registered account, you will receive an email.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{this.renderError("_general")}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const tfaPanel = (
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
{this.renderError("_general")}
|
|
||||||
{this.renderInput("securityCode", "Authenticate")}
|
|
||||||
{this.renderButton("Authenticate")}
|
|
||||||
<Link to="#" onClick={this.authenticationWorkAround}>
|
|
||||||
My Authenticator is not working
|
|
||||||
</Link>
|
|
||||||
</form>
|
</form>
|
||||||
);
|
{isInNextStage && (
|
||||||
|
<div className="forgottenLink">
|
||||||
|
{renderButton(
|
||||||
|
"Forgotten Password",
|
||||||
|
form.state.errors,
|
||||||
|
"forgot-password",
|
||||||
|
handleForgetPassword,
|
||||||
|
"forgot-password",
|
||||||
|
validEmail,
|
||||||
|
ButtonType.secondary,
|
||||||
|
true,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{emailSent && (
|
||||||
|
<div className="alert alert-info emailSent">
|
||||||
|
If you have a registered account, you will receive an email.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{renderError("_general", form.state.errors)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
const tfaPanel = (
|
||||||
<div>
|
<form onSubmit={handleSubmit}>
|
||||||
{requestTfaRemoval
|
{renderError("_general", form.state.errors)}
|
||||||
? requestTfaRemovalPanel
|
{renderInput(
|
||||||
: tfaNeeded
|
"securityCode",
|
||||||
? tfaPanel
|
"Authenticate",
|
||||||
: loginPanel}
|
form.state.data,
|
||||||
</div>
|
form.state.errors,
|
||||||
);
|
InputType.text,
|
||||||
}
|
false,
|
||||||
}
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
{renderButton("Authenticate", form.state.errors, "authenticate")}
|
||||||
|
<Link to="#" onClick={authenticationWorkAround}>
|
||||||
|
My Authenticator is not working
|
||||||
|
</Link>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{requestTfaRemoval
|
||||||
|
? requestTfaRemovalPanel
|
||||||
|
: tfaNeeded
|
||||||
|
? tfaPanel
|
||||||
|
: loginPanel}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default LoginForm;
|
export default LoginForm;
|
||||||
|
|||||||
@ -5,7 +5,9 @@ import { toast } from "react-toastify";
|
|||||||
import { useForm } from "../../../components/common/useForm";
|
import { useForm } from "../../../components/common/useForm";
|
||||||
import { InputType } from "../../../components/common/Input";
|
import { InputType } from "../../../components/common/Input";
|
||||||
import Loading from "../../../components/common/Loading";
|
import Loading from "../../../components/common/Loading";
|
||||||
import TemplateFiller from "../../../components/common/TemplateFiller";
|
import TemplateFiller, {
|
||||||
|
TemplateFillerHandle,
|
||||||
|
} from "../../../components/common/TemplateFiller";
|
||||||
import { GeneralIdRef, MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
|
import { GeneralIdRef, MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
|
||||||
import {
|
import {
|
||||||
CustomFieldValue,
|
CustomFieldValue,
|
||||||
@ -31,7 +33,7 @@ const SpecificationsDetails: React.FC<SpecificationsDetailsProps> = ({
|
|||||||
siteId: string;
|
siteId: string;
|
||||||
specificationId?: string;
|
specificationId?: string;
|
||||||
}>();
|
}>();
|
||||||
const TemplateFillerRef = useRef<TemplateFiller>(null);
|
const TemplateFillerRef = useRef<TemplateFillerHandle>(null);
|
||||||
|
|
||||||
const labelName = "Name";
|
const labelName = "Name";
|
||||||
const labelPrintSpecification = "Print Specification";
|
const labelPrintSpecification = "Print Specification";
|
||||||
@ -257,7 +259,9 @@ const SpecificationsDetails: React.FC<SpecificationsDetailsProps> = ({
|
|||||||
|
|
||||||
<TemplateFiller
|
<TemplateFiller
|
||||||
templateId={formTemplate}
|
templateId={formTemplate}
|
||||||
formInstanceId={form.state.data.formInstanceId}
|
formInstanceId={
|
||||||
|
form.state.data.formInstanceId as GeneralIdRef | undefined
|
||||||
|
}
|
||||||
ref={TemplateFillerRef}
|
ref={TemplateFillerRef}
|
||||||
onValidationChanged={handleValidationChanged}
|
onValidationChanged={handleValidationChanged}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -1,204 +1,346 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Joi from "joi";
|
import Joi from "joi";
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
import Form, { FormState, FormData } from "../../components/common/Form";
|
import { useForm } from "../../components/common/useForm";
|
||||||
import profileService from "./services/profileService";
|
import profileService from "./services/profileService";
|
||||||
import { InputType } from "../../components/common/Input";
|
import { InputType } from "../../components/common/Input";
|
||||||
import { TwoFactorAuthenticationSettings } from "./models/TwoFactorAuthenticationSettings";
|
import { TwoFactorAuthenticationSettings } from "./models/TwoFactorAuthenticationSettings";
|
||||||
import Loading from "../../components/common/Loading";
|
import Loading from "../../components/common/Loading";
|
||||||
|
import {
|
||||||
|
renderError,
|
||||||
|
renderButton,
|
||||||
|
renderInput,
|
||||||
|
renderToggle,
|
||||||
|
renderDropSection,
|
||||||
|
} from "../../components/common/formHelpers";
|
||||||
|
|
||||||
export interface ProfileStateData extends FormData {
|
const Profile: React.FC = () => {
|
||||||
firstName: string;
|
const labelFirstName = "First Name";
|
||||||
middleNames: string;
|
const labelMiddleNames = "Middle Name(s)";
|
||||||
lastName: string;
|
const labelLastName = "Last Name";
|
||||||
email: string;
|
const labelEmail = "E-Mail";
|
||||||
newPassword: string;
|
const labelNewPassword = "New Password";
|
||||||
confirmPassword: string;
|
const labelConfirmPassword = "Confirm Password";
|
||||||
originalUsingTwoFactorAuthentication: boolean;
|
const labelUsingTwoFactorAuthentication = "2 Factor Authentication";
|
||||||
usingTwoFactorAuthentication: boolean;
|
const labelTfaCode = "Authentication code";
|
||||||
tfaCode: string;
|
const labelApply = "Save";
|
||||||
}
|
|
||||||
|
|
||||||
export interface ProfileState extends FormState {
|
const [twoFactorAuthenticationSettings, setTwoFactorAuthenticationSettings] =
|
||||||
data: ProfileStateData;
|
useState<TwoFactorAuthenticationSettings>({
|
||||||
twoFactorAuthenticationSettings: TwoFactorAuthenticationSettings;
|
manualEntrySetupCode: "",
|
||||||
}
|
qrCodeImageUrl: "",
|
||||||
|
});
|
||||||
|
|
||||||
class Profile extends Form<any, any, ProfileState> {
|
const form = useForm({
|
||||||
state = {
|
loaded: false,
|
||||||
loaded: false,
|
data: {
|
||||||
passwordMaxLenght : 255,
|
firstName: "",
|
||||||
data: {
|
middleNames: "",
|
||||||
firstName: "",
|
lastName: "",
|
||||||
middleNames: "",
|
email: "",
|
||||||
lastName: "",
|
newPassword: "",
|
||||||
email: "",
|
confirmPassword: "",
|
||||||
newPassword: "",
|
originalUsingTwoFactorAuthentication: false,
|
||||||
confirmPassword: "",
|
usingTwoFactorAuthentication: false,
|
||||||
originalUsingTwoFactorAuthentication: false,
|
tfaCode: "",
|
||||||
usingTwoFactorAuthentication: false,
|
},
|
||||||
tfaCode: ""
|
errors: {},
|
||||||
},
|
redirect: "",
|
||||||
errors: {},
|
});
|
||||||
twoFactorAuthenticationSettings: {
|
|
||||||
manualEntrySetupCode: "",
|
|
||||||
qrCodeImageUrl: "",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
labelFirstName = "First Name";
|
form.schema = {
|
||||||
labelMiddleNames = "Middle Name(s)";
|
firstName: Joi.string().required().label(labelFirstName),
|
||||||
labelLastName = "Last Name";
|
middleNames: Joi.string().allow("").required().label(labelMiddleNames),
|
||||||
labelEmail = "E-Mail";
|
lastName: Joi.string().required().label(labelLastName),
|
||||||
labelNewPassword = "New Password";
|
email: Joi.string()
|
||||||
labelConfirmPassword = "Confirm Password";
|
.required()
|
||||||
|
.email({ tlds: { allow: false } })
|
||||||
labelUsingTwoFactorAuthentication = "2 Factor Authentication";
|
.label(labelEmail),
|
||||||
labelTfaCode = "Authentication code";
|
newPassword: Joi.string().allow("").min(5).label(labelNewPassword),
|
||||||
labelApply = "Save";
|
confirmPassword: Joi.string()
|
||||||
|
.when("newPassword", {
|
||||||
schema = {
|
is: "",
|
||||||
firstName: Joi.string().required().label(this.labelFirstName),
|
then: Joi.allow("").optional(),
|
||||||
middleNames: Joi.string().allow("").required().label(this.labelMiddleNames),
|
otherwise: Joi.valid(Joi.ref("newPassword")).error(() => {
|
||||||
lastName: Joi.string().required().label(this.labelLastName),
|
const e = new Error("Passwords must match");
|
||||||
email: Joi.string()
|
e.name = "confirmPassword";
|
||||||
|
return e;
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.label(labelConfirmPassword),
|
||||||
|
originalUsingTwoFactorAuthentication: Joi.boolean().required(),
|
||||||
|
usingTwoFactorAuthentication: Joi.boolean()
|
||||||
|
.required()
|
||||||
|
.label(labelUsingTwoFactorAuthentication),
|
||||||
|
tfaCode: Joi.string()
|
||||||
|
.when("originalUsingTwoFactorAuthentication", {
|
||||||
|
is: Joi.ref("usingTwoFactorAuthentication"),
|
||||||
|
then: Joi.allow("").optional(),
|
||||||
|
otherwise: Joi.when("usingTwoFactorAuthentication", {
|
||||||
|
is: true,
|
||||||
|
then: Joi.string()
|
||||||
|
.length(6)
|
||||||
.required()
|
.required()
|
||||||
.email({ tlds: { allow: false } })
|
.error(() => {
|
||||||
.label(this.labelEmail),
|
const e = new Error(
|
||||||
newPassword: Joi.string().allow("").min(5).label(this.labelNewPassword),
|
"You must enter the code from the authenicator",
|
||||||
confirmPassword: Joi.string()
|
);
|
||||||
.when("newPassword", {
|
e.name = "tfaCode";
|
||||||
is: "",
|
return e;
|
||||||
then: Joi.allow("").optional(),
|
}),
|
||||||
otherwise: Joi.valid(Joi.ref("newPassword")).error(() => {
|
otherwise: Joi.allow("").optional(),
|
||||||
const e = new Error("Passwords must match");
|
}),
|
||||||
e.name = "confirmPassword";
|
})
|
||||||
return e;
|
.label(labelTfaCode),
|
||||||
}),
|
};
|
||||||
})
|
|
||||||
.label(this.labelConfirmPassword),
|
|
||||||
|
|
||||||
originalUsingTwoFactorAuthentication: Joi.boolean().required(),
|
const loadProfile = async () => {
|
||||||
usingTwoFactorAuthentication: Joi.boolean().required().label(this.labelUsingTwoFactorAuthentication),
|
try {
|
||||||
|
const profile = await profileService.getMyProfile();
|
||||||
|
if (profile) {
|
||||||
|
const {
|
||||||
|
firstName,
|
||||||
|
middleNames,
|
||||||
|
lastName,
|
||||||
|
email,
|
||||||
|
usingTwoFactorAuthentication,
|
||||||
|
twoFactorAuthenticationSettings,
|
||||||
|
} = profile;
|
||||||
|
|
||||||
tfaCode: Joi.string()
|
const data = {
|
||||||
.when("originalUsingTwoFactorAuthentication", {
|
firstName,
|
||||||
is: Joi.ref("usingTwoFactorAuthentication"),
|
middleNames,
|
||||||
then: Joi.allow("").optional(),
|
lastName,
|
||||||
otherwise: Joi.when("usingTwoFactorAuthentication", {
|
email,
|
||||||
is: true,
|
newPassword: "",
|
||||||
then: Joi.string()
|
confirmPassword: "",
|
||||||
.length(6)
|
originalUsingTwoFactorAuthentication: usingTwoFactorAuthentication,
|
||||||
.required()
|
usingTwoFactorAuthentication,
|
||||||
.error(() => {
|
tfaCode: "",
|
||||||
const e = new Error("You must enter the code from the authenicator");
|
};
|
||||||
e.name = "tfaCode";
|
form.setState({ loaded: true, data });
|
||||||
return e;
|
setTwoFactorAuthenticationSettings(twoFactorAuthenticationSettings);
|
||||||
}),
|
}
|
||||||
otherwise: Joi.allow("").optional(),
|
} catch (ex: any) {
|
||||||
}),
|
form.handleGeneralError(ex);
|
||||||
})
|
|
||||||
.label(this.labelTfaCode),
|
|
||||||
};
|
|
||||||
|
|
||||||
async componentDidMount() {
|
|
||||||
try {
|
|
||||||
const profile = await profileService.getMyProfile();
|
|
||||||
if (profile) {
|
|
||||||
const { firstName, middleNames, lastName, email, usingTwoFactorAuthentication, twoFactorAuthenticationSettings } = profile;
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
firstName,
|
|
||||||
middleNames,
|
|
||||||
lastName,
|
|
||||||
email,
|
|
||||||
newPassword: "",
|
|
||||||
confirmPassword: "",
|
|
||||||
originalUsingTwoFactorAuthentication: usingTwoFactorAuthentication,
|
|
||||||
usingTwoFactorAuthentication,
|
|
||||||
tfaCode: ""
|
|
||||||
};
|
|
||||||
this.setState({ loaded: true, data, twoFactorAuthenticationSettings });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(ex: any) {
|
|
||||||
this.handleGeneralError(ex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
doSubmit = async (buttonName : string) => {
|
useEffect(() => {
|
||||||
try {
|
void loadProfile();
|
||||||
const { firstName, middleNames, lastName, email, usingTwoFactorAuthentication, tfaCode, newPassword, confirmPassword } = this.state.data;
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
let password = "";
|
const doSubmit = async (buttonName: string) => {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
firstName,
|
||||||
|
middleNames,
|
||||||
|
lastName,
|
||||||
|
email,
|
||||||
|
usingTwoFactorAuthentication,
|
||||||
|
tfaCode,
|
||||||
|
newPassword,
|
||||||
|
confirmPassword,
|
||||||
|
} = form.state.data;
|
||||||
|
|
||||||
if (newPassword === confirmPassword) password = newPassword;
|
let password = "";
|
||||||
|
if (newPassword === confirmPassword) password = newPassword as string;
|
||||||
|
|
||||||
const response = await profileService.putMyProfile(firstName, middleNames, lastName, email, usingTwoFactorAuthentication, tfaCode, password);
|
const response = await profileService.putMyProfile(
|
||||||
if (response) {
|
firstName as string,
|
||||||
await this.componentDidMount();
|
middleNames as string,
|
||||||
toast.info("Your profile settings have been saved");
|
lastName as string,
|
||||||
}
|
email as string,
|
||||||
}
|
usingTwoFactorAuthentication as boolean,
|
||||||
catch(ex: any) {
|
tfaCode as string,
|
||||||
this.handleGeneralError(ex);
|
password,
|
||||||
}
|
);
|
||||||
};
|
if (response) {
|
||||||
|
await loadProfile();
|
||||||
render() {
|
toast.info("Your profile settings have been saved");
|
||||||
const { loaded, twoFactorAuthenticationSettings, passwordMaxLenght } = this.state;
|
}
|
||||||
|
} catch (ex: any) {
|
||||||
const { usingTwoFactorAuthentication, newPassword } = this.state.data;
|
form.handleGeneralError(ex);
|
||||||
|
|
||||||
const tfaEnabled = usingTwoFactorAuthentication ? "Enabled" : "Disabled";
|
|
||||||
|
|
||||||
let tfaImageBlock = null;
|
|
||||||
if (twoFactorAuthenticationSettings)
|
|
||||||
tfaImageBlock = (
|
|
||||||
<React.Fragment>
|
|
||||||
<label>{twoFactorAuthenticationSettings.manualEntrySetupCode}</label>
|
|
||||||
<img src={twoFactorAuthenticationSettings.qrCodeImageUrl} alt={twoFactorAuthenticationSettings.manualEntrySetupCode} />
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
|
|
||||||
let tfaSection : JSX.Element;
|
|
||||||
tfaSection = (
|
|
||||||
<div>
|
|
||||||
{this.renderToggle("usingTwoFactorAuthentication", this.labelUsingTwoFactorAuthentication)}
|
|
||||||
{tfaImageBlock}
|
|
||||||
{this.renderInput("tfaCode", this.labelTfaCode)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
let passwordSection = <React.Fragment>{this.renderInput("newPassword", this.labelNewPassword, InputType.password)}</React.Fragment>;
|
|
||||||
|
|
||||||
if (newPassword !== "")
|
|
||||||
passwordSection = (
|
|
||||||
<React.Fragment>
|
|
||||||
{this.renderInput("newPassword", this.labelNewPassword, InputType.password, false, undefined, "", passwordMaxLenght)}
|
|
||||||
{this.renderInput("confirmPassword", this.labelConfirmPassword, InputType.password, false, undefined, "", passwordMaxLenght)}
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Loading loaded={loaded}>
|
|
||||||
<h1>Profile</h1>
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
{this.renderError("_general")}
|
|
||||||
{this.renderInput("email", this.labelEmail, InputType.text, true)}
|
|
||||||
{this.renderInput("firstName", this.labelFirstName)}
|
|
||||||
{this.renderInput("middleNames", this.labelMiddleNames)}
|
|
||||||
{this.renderInput("lastName", this.labelLastName)}
|
|
||||||
|
|
||||||
{passwordSection}
|
|
||||||
|
|
||||||
{this.renderDropSection("turnOnTfa", <label>Two Factor Authentication {tfaEnabled}</label>, tfaSection)}
|
|
||||||
<br/>
|
|
||||||
{this.renderButton(this.labelApply)}
|
|
||||||
</form>
|
|
||||||
</Loading>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
form.handleSubmit(e, doSubmit);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { loaded } = form.state;
|
||||||
|
const { usingTwoFactorAuthentication, newPassword } = form.state.data;
|
||||||
|
const passwordMaxLength = 255;
|
||||||
|
|
||||||
|
const tfaEnabled = usingTwoFactorAuthentication ? "Enabled" : "Disabled";
|
||||||
|
|
||||||
|
let tfaImageBlock = null;
|
||||||
|
if (twoFactorAuthenticationSettings)
|
||||||
|
tfaImageBlock = (
|
||||||
|
<React.Fragment>
|
||||||
|
<label>{twoFactorAuthenticationSettings.manualEntrySetupCode}</label>
|
||||||
|
<img
|
||||||
|
src={twoFactorAuthenticationSettings.qrCodeImageUrl}
|
||||||
|
alt={twoFactorAuthenticationSettings.manualEntrySetupCode}
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
const tfaSection = (
|
||||||
|
<div>
|
||||||
|
{renderToggle(
|
||||||
|
"usingTwoFactorAuthentication",
|
||||||
|
labelUsingTwoFactorAuthentication,
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
form.handleToggleChange,
|
||||||
|
)}
|
||||||
|
{tfaImageBlock}
|
||||||
|
{renderInput(
|
||||||
|
"tfaCode",
|
||||||
|
labelTfaCode,
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
InputType.text,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
let passwordSection = (
|
||||||
|
<React.Fragment>
|
||||||
|
{renderInput(
|
||||||
|
"newPassword",
|
||||||
|
labelNewPassword,
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
InputType.password,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newPassword !== "")
|
||||||
|
passwordSection = (
|
||||||
|
<React.Fragment>
|
||||||
|
{renderInput(
|
||||||
|
"newPassword",
|
||||||
|
labelNewPassword,
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
InputType.password,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
passwordMaxLength,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
{renderInput(
|
||||||
|
"confirmPassword",
|
||||||
|
labelConfirmPassword,
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
InputType.password,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
passwordMaxLength,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Loading loaded={loaded}>
|
||||||
|
<h1>Profile</h1>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{renderError("_general", form.state.errors)}
|
||||||
|
{renderInput(
|
||||||
|
"email",
|
||||||
|
labelEmail,
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
InputType.text,
|
||||||
|
true,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
{renderInput(
|
||||||
|
"firstName",
|
||||||
|
labelFirstName,
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
InputType.text,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
{renderInput(
|
||||||
|
"middleNames",
|
||||||
|
labelMiddleNames,
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
InputType.text,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
{renderInput(
|
||||||
|
"lastName",
|
||||||
|
labelLastName,
|
||||||
|
form.state.data,
|
||||||
|
form.state.errors,
|
||||||
|
InputType.text,
|
||||||
|
false,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
undefined,
|
||||||
|
form.handleChange,
|
||||||
|
)}
|
||||||
|
|
||||||
|
{passwordSection}
|
||||||
|
|
||||||
|
{renderDropSection(
|
||||||
|
"turnOnTfa",
|
||||||
|
<label>Two Factor Authentication {tfaEnabled}</label>,
|
||||||
|
tfaSection,
|
||||||
|
form.state.errors,
|
||||||
|
)}
|
||||||
|
<br />
|
||||||
|
{renderButton(labelApply, form.state.errors, "apply")}
|
||||||
|
</form>
|
||||||
|
</Loading>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Profile;
|
export default Profile;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user