Next round of refactoring done
This commit is contained in:
parent
04c1875d5d
commit
f3deae0842
@ -1,7 +1,10 @@
|
|||||||
{
|
{
|
||||||
"Activate": "Activate",
|
"Activate": "Activate",
|
||||||
"Admin": "Admin",
|
"Admin": "Admin",
|
||||||
|
"AnEmailWithPasswordResetLinkHasBeenSent": "An email with a password reset link has been sent.",
|
||||||
"AnErrorOccurred": "An error occurred",
|
"AnErrorOccurred": "An error occurred",
|
||||||
|
"Application": "Application",
|
||||||
|
"Applications": "Applications",
|
||||||
"AuditLog": "Audit Logs",
|
"AuditLog": "Audit Logs",
|
||||||
"AuditLogs": "Audit Logs",
|
"AuditLogs": "Audit Logs",
|
||||||
"BlockedIPAddresses": "Blocked IP addresses",
|
"BlockedIPAddresses": "Blocked IP addresses",
|
||||||
@ -13,42 +16,49 @@
|
|||||||
"ConfirmPassword": "Confirm Password",
|
"ConfirmPassword": "Confirm Password",
|
||||||
"CustomFieldManager": "Custom Field Manager",
|
"CustomFieldManager": "Custom Field Manager",
|
||||||
"CustomFields": "Custom Fields",
|
"CustomFields": "Custom Fields",
|
||||||
|
"DisableAuthenticator": "Disable Authenticator",
|
||||||
"DisplayName": "Display Name",
|
"DisplayName": "Display Name",
|
||||||
"EntityDisplayName": "Entity Display Name",
|
|
||||||
"e-print": "e-print",
|
"e-print": "e-print",
|
||||||
"e-suite": "e-suite",
|
"e-suite": "e-suite",
|
||||||
|
"e-suiteLogo": "e-suite logo",
|
||||||
|
"EntityDisplayName": "Entity Display Name",
|
||||||
"ErrorLogs": "Error Logs",
|
"ErrorLogs": "Error Logs",
|
||||||
"ExceptionJson": "Exception JSON",
|
"ExceptionJson": "Exception JSON",
|
||||||
"ExceptionLogs": "Exception Logs",
|
"ExceptionLogs": "Exception Logs",
|
||||||
|
"FailedToDisableAuthenticator": "Failed to disable authenticator:",
|
||||||
"Forms": "Forms",
|
"Forms": "Forms",
|
||||||
"FormTemplateManager": "Form Template Manager",
|
"FormTemplateManager": "Form Template Manager",
|
||||||
"Glossaries": "Glossaries",
|
"Glossaries": "Glossaries",
|
||||||
"GlossaryManager": "Glossary Manager",
|
"GlossaryManager": "Glossary Manager",
|
||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
"Id": "Id",
|
"Id": "Id",
|
||||||
"Application": "Application",
|
|
||||||
"Message": "Message",
|
|
||||||
"ShowJSON": "Show JSON",
|
|
||||||
"ShowStackTrace": "Show stack trace",
|
|
||||||
"OccuredAt": "Occured At",
|
|
||||||
"IPAddress": "IP Address",
|
"IPAddress": "IP Address",
|
||||||
"IPAddressUnblocked": "IP Address '{{ip}}' unblocked.",
|
"IPAddressUnblocked": "IP Address '{{ip}}' unblocked.",
|
||||||
"Loading": "Loading",
|
"Loading": "Loading",
|
||||||
|
"LoggingOut": "Logging out",
|
||||||
|
"Message": "Message",
|
||||||
"Name": "Name",
|
"Name": "Name",
|
||||||
"NumberOfAttempts": "Number Of Attempts",
|
"NewPassword": "New Password",
|
||||||
"NewValue": "New Value",
|
"NewValue": "New Value",
|
||||||
|
"NotFound": "Not found",
|
||||||
|
"NumberOfAttempts": "Number Of Attempts",
|
||||||
|
"OccuredAt": "Occured At",
|
||||||
"OldValue": "Old Value",
|
"OldValue": "Old Value",
|
||||||
"Password": "Password",
|
"Password": "Password",
|
||||||
"PasswordIsRequired": "Password is required",
|
"PasswordIsRequired": "Password is required",
|
||||||
"PasswordMinLength": "Password must be at least {{minPasswordLength}} characters",
|
"PasswordMinLength": "Password must be at least {{minPasswordLength}} characters",
|
||||||
"PasswordsMustMatch": "You need to confirm by typing exactly the same as the new password",
|
"PasswordsMustMatch": "You need to confirm by typing exactly the same as the new password",
|
||||||
"PressAgainToUnblock": "Press again to unblock",
|
"PressAgainToUnblock": "Press again to unblock",
|
||||||
|
"ResetPassword": "Reset Password",
|
||||||
|
"Save": "Save",
|
||||||
"Sequence": "Sequence",
|
"Sequence": "Sequence",
|
||||||
"SequenceManager": "Sequence Manager",
|
"SequenceManager": "Sequence Manager",
|
||||||
|
"ShowJSON": "Show JSON",
|
||||||
|
"ShowStackTrace": "Show stack trace",
|
||||||
"SiteManager": "Site Manager",
|
"SiteManager": "Site Manager",
|
||||||
"SpecificationManager": "Specification Manager",
|
"SpecificationManager": "Specification Manager",
|
||||||
"StackTrace": "Stack Trace",
|
|
||||||
"SsoManager": "Sso Manager",
|
"SsoManager": "Sso Manager",
|
||||||
|
"StackTrace": "Stack Trace",
|
||||||
"Support": "Support",
|
"Support": "Support",
|
||||||
"SupportingData": "Supporting Data",
|
"SupportingData": "Supporting Data",
|
||||||
"Timing": "Timing",
|
"Timing": "Timing",
|
||||||
@ -56,5 +66,7 @@
|
|||||||
"UnblockedInMinutes": "Unblocked In (Minutes)",
|
"UnblockedInMinutes": "Unblocked In (Minutes)",
|
||||||
"UserManager": "User Manager",
|
"UserManager": "User Manager",
|
||||||
"UserName": "User Name",
|
"UserName": "User Name",
|
||||||
|
"UsernameIsRequired": "Username is required",
|
||||||
|
"UsernameMustBeValidEmail": "Username must be a valid email",
|
||||||
"Users": "Users"
|
"Users": "Users"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,26 @@
|
|||||||
import { FunctionComponent } from "react";
|
import { FunctionComponent } from "react";
|
||||||
import logo from "./E-SUITE_logo.svg"
|
import logo from "./E-SUITE_logo.svg";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Namespaces } from "../i18n/i18n";
|
||||||
|
|
||||||
interface LogoProps {
|
interface LogoProps {
|
||||||
className? : string;
|
className?: string;
|
||||||
height? : string
|
height?: string;
|
||||||
width? : string
|
width?: string;
|
||||||
alt?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Logo: FunctionComponent<LogoProps> = (props:LogoProps) => {
|
const Logo: FunctionComponent<LogoProps> = (props: LogoProps) => {
|
||||||
return ( <img className={props.className} height={props.height} width={props.width} alt={props.alt} src={logo}/>);
|
const { t } = useTranslation(Namespaces.Common);
|
||||||
}
|
|
||||||
|
return (
|
||||||
export default Logo;
|
<img
|
||||||
|
className={props.className}
|
||||||
|
height={props.height}
|
||||||
|
width={props.width}
|
||||||
|
alt={t("e-suiteLogo") as string}
|
||||||
|
src={logo}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Logo;
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Namespaces } from "../../../i18n/i18n";
|
import { Namespaces } from "../../../i18n/i18n";
|
||||||
|
|
||||||
import ExpandableCell from "../../../components/common/ExpandableCell";
|
import ExpandableCell from "../../../components/common/ExpandableCell";
|
||||||
import { max } from "date-fns";
|
|
||||||
|
|
||||||
export default function ErrorLogsTable(
|
export default function ErrorLogsTable(
|
||||||
props: PublishedTableProps<ErrorLog>,
|
props: PublishedTableProps<ErrorLog>,
|
||||||
|
|||||||
@ -1,63 +1,63 @@
|
|||||||
import Form, { FormState, FormData } from "../../../components/common/Form";
|
import React, { useState } from "react";
|
||||||
import Joi from "joi";
|
|
||||||
import authentication from "../services/authenticationService";
|
import authentication from "../services/authenticationService";
|
||||||
import { IEmailUserAction } from "../models/IEmailUserAction";
|
import { IEmailUserAction } from "../models/IEmailUserAction";
|
||||||
|
import { FormData } from "../../../components/common/Form";
|
||||||
import Button, { ButtonType } from "../../../components/common/Button";
|
import Button, { ButtonType } from "../../../components/common/Button";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Namespaces } from "../../../i18n/i18n";
|
||||||
|
|
||||||
export interface EmailUserActionDiableTwoFactorAuthenticationData extends FormData {
|
export interface EmailUserActionDiableTwoFactorAuthenticationData extends FormData {
|
||||||
authenticatorDisabled: boolean;
|
authenticatorDisabled: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmailUserActionDiableTwoFactorAuthenticationState extends FormState {
|
interface Props {
|
||||||
data: EmailUserActionDiableTwoFactorAuthenticationData;
|
emailUserAction: IEmailUserAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmailUserActionDiableTwoFactorAuthentication extends Form<any, any, EmailUserActionDiableTwoFactorAuthenticationState> {
|
const EmailUserActionDiableTwoFactorAuthentication: React.FC<Props> = ({
|
||||||
state = {
|
emailUserAction,
|
||||||
loaded: true,
|
}) => {
|
||||||
data: { authenticatorDisabled: false },
|
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||||
errors: {},
|
const [authenticatorDisabled, setAuthenticatorDisabled] = useState(false);
|
||||||
|
|
||||||
|
const LABEL_DISABLE_AUTHENTICATOR = t("DisableAuthenticator");
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
const action: IEmailUserAction = {
|
||||||
|
email: emailUserAction.email,
|
||||||
|
token: emailUserAction.token,
|
||||||
|
password: "",
|
||||||
|
emailActionType: emailUserAction.emailActionType,
|
||||||
};
|
};
|
||||||
|
|
||||||
labelChangePassword = "Disable Authenticator";
|
try {
|
||||||
|
const callResult = await authentication.completeEmailAction(action);
|
||||||
schema = {
|
if (callResult === 1) {
|
||||||
authenticatorDisabled: Joi.boolean(),
|
setAuthenticatorDisabled(true);
|
||||||
};
|
}
|
||||||
|
} catch (error) {
|
||||||
doSubmit = async () => {
|
console.error(t("FailedToDisableAuthenticator"), error);
|
||||||
const { emailUserAction } = this.props;
|
|
||||||
|
|
||||||
const action: IEmailUserAction = {
|
|
||||||
email: emailUserAction.email,
|
|
||||||
token: emailUserAction.token,
|
|
||||||
password: "",
|
|
||||||
emailActionType: emailUserAction.emailActionType,
|
|
||||||
};
|
|
||||||
|
|
||||||
const callResult = await authentication.completeEmailAction(action);
|
|
||||||
if (callResult === 1) {
|
|
||||||
let data = { ...this.state.data };
|
|
||||||
data.authenticatorDisabled = true;
|
|
||||||
this.setState({ data });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { authenticatorDisabled } = this.state.data;
|
|
||||||
|
|
||||||
if (authenticatorDisabled) {
|
|
||||||
return <div>Your authenticator has been disabled. You can now log in without two factor authentication</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div>Disable two factor authentication</div>
|
|
||||||
|
|
||||||
<Button buttonType={ButtonType.link} onClick={this.doSubmit}>Disable Authenticator</Button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
|
if (authenticatorDisabled) {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
Your authenticator has been disabled. You can now log in without two
|
||||||
|
factor authentication
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div>Disable two factor authentication</div>
|
||||||
|
|
||||||
|
<Button buttonType={ButtonType.link} onClick={handleSubmit}>
|
||||||
|
{LABEL_DISABLE_AUTHENTICATOR}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default EmailUserActionDiableTwoFactorAuthentication;
|
export default EmailUserActionDiableTwoFactorAuthentication;
|
||||||
|
|||||||
@ -1,136 +1,173 @@
|
|||||||
import Joi from "joi";
|
import React, { useState } from "react";
|
||||||
import Form, { businessValidationResult, FormData, FormState } from "../../../components/common/Form";
|
|
||||||
import { InputType } from "../../../components/common/Input";
|
import { InputType } from "../../../components/common/Input";
|
||||||
import { IEmailUserAction } from "../models/IEmailUserAction";
|
import { IEmailUserAction } from "../models/IEmailUserAction";
|
||||||
import authentication from "../services/authenticationService";
|
import authentication from "../services/authenticationService";
|
||||||
|
import { FormData } from "../../../components/common/Form";
|
||||||
|
import Input from "../../../components/common/Input";
|
||||||
|
import Button, { ButtonType } from "../../../components/common/Button";
|
||||||
|
import ErrorBlock from "../../../components/common/ErrorBlock";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Namespaces } from "../../../i18n/i18n";
|
||||||
|
|
||||||
export interface EmailUserActionPasswordResetData extends FormData {
|
export interface EmailUserActionPasswordResetData extends FormData {
|
||||||
password: string;
|
password: string;
|
||||||
confirmPassword: string;
|
confirmPassword: string;
|
||||||
passwordChanged: boolean;
|
passwordChanged: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EmailUserActionPasswordResetState extends FormState {
|
interface Props {
|
||||||
data: EmailUserActionPasswordResetData;
|
emailUserAction: IEmailUserAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EmailUserActionPasswordReset extends Form<any, any, EmailUserActionPasswordResetState> {
|
const EmailUserActionPasswordReset: React.FC<Props> = ({ emailUserAction }) => {
|
||||||
state = {
|
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||||
loaded: true,
|
const [password, setPassword] = useState("");
|
||||||
passwordMaxLenght: 255,
|
const [confirmPassword, setConfirmPassword] = useState("");
|
||||||
data: { password: "", confirmPassword: "", passwordChanged: false },
|
const [passwordChanged, setPasswordChanged] = useState(false);
|
||||||
errors: {},
|
const [generalError, setGeneralError] = useState("");
|
||||||
hasTwelveCharacters: false,
|
const [errors, setErrors] = useState<{ [key: string]: string }>({});
|
||||||
hasSpecialCharacter: false,
|
const [hasTwelveCharacters, setHasTwelveCharacters] = useState(false);
|
||||||
hasUppercaseLetter: false,
|
const [hasSpecialCharacter, setHasSpecialCharacter] = useState(false);
|
||||||
hasLowercaseLetter: false,
|
const [hasUppercaseLetter, setHasUppercaseLetter] = useState(false);
|
||||||
hasNumber: false
|
const [hasLowercaseLetter, setHasLowercaseLetter] = useState(false);
|
||||||
};
|
const [hasNumber, setHasNumber] = useState(false);
|
||||||
|
|
||||||
labelPassword = "New Password";
|
const LABEL_PASSWORD = t("NewPassword");
|
||||||
labelConfirmPassword = "Confirm Password";
|
const LABEL_CONFIRM_PASSWORD = t("ConfirmPassword");
|
||||||
labelChangePassword = "Save";
|
const LABEL_CHANGE_PASSWORD = t("Save");
|
||||||
|
const PASSWORD_MAX_LENGTH = 255;
|
||||||
|
|
||||||
schema = {
|
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
password: Joi.string().required().min(12).label(this.labelPassword),
|
const newPassword = e.currentTarget.value;
|
||||||
confirmPassword: Joi.string()
|
setPassword(newPassword);
|
||||||
.when("password", {
|
setConfirmPassword("");
|
||||||
is: "",
|
setHasNumber(/\d+/g.test(newPassword));
|
||||||
then: Joi.optional(),
|
setHasLowercaseLetter(/[a-z]/g.test(newPassword));
|
||||||
otherwise: Joi.valid(Joi.ref("password")).error(() => {
|
setHasUppercaseLetter(/[A-Z]/g.test(newPassword));
|
||||||
const e = new Error("Passwords must match");
|
setHasSpecialCharacter(
|
||||||
e.name = "confirmPassword";
|
/[ ~`! @#$%^&*()_+\-=[\]{};:\\|,.'"<>/?]/.test(newPassword),
|
||||||
return e;
|
);
|
||||||
}),
|
setHasTwelveCharacters(newPassword.length >= 12);
|
||||||
})
|
};
|
||||||
.label(this.labelConfirmPassword),
|
|
||||||
|
|
||||||
passwordChanged: Joi.boolean(),
|
const handleConfirmPasswordChange = (
|
||||||
};
|
e: React.ChangeEvent<HTMLInputElement>,
|
||||||
|
) => {
|
||||||
|
setConfirmPassword(e.currentTarget.value);
|
||||||
|
};
|
||||||
|
|
||||||
BusinessValidation(): businessValidationResult | null {
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
const { password, confirmPassword } = this.state.data;
|
e.preventDefault();
|
||||||
|
const newErrors: { [key: string]: string } = {};
|
||||||
|
|
||||||
if (password !== confirmPassword) {
|
const minPasswordLength = 12;
|
||||||
return {
|
|
||||||
details: [
|
|
||||||
{
|
|
||||||
path: "confirmPassword",
|
|
||||||
message: "You need to confirm by typing exactly the same as the new password",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
// Validation
|
||||||
|
if (!password) {
|
||||||
|
newErrors.password = t("PasswordIsRequired");
|
||||||
|
} else if (password.length < minPasswordLength) {
|
||||||
|
newErrors.password = t("PasswordMinLength", {
|
||||||
|
minPasswordLength: minPasswordLength,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handlePasswordChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
if (password && password !== confirmPassword) {
|
||||||
const { data } = this.state;
|
newErrors.confirmPassword = t("PasswordsMustMatch");
|
||||||
data.password = e.currentTarget.value;
|
|
||||||
data.confirmPassword = "";
|
|
||||||
const stateData = this.state;
|
|
||||||
stateData.hasNumber = /\d+/g.test(data.password);
|
|
||||||
stateData.hasLowercaseLetter = /[a-z]/g.test(data.password);
|
|
||||||
stateData.hasUppercaseLetter = /[A-Z]/g.test(data.password);;
|
|
||||||
stateData.hasSpecialCharacter = /[ ~`! @#$%^&*()_+\-=[\]{};:\\|,.'"<>/?]/.test(data.password);
|
|
||||||
stateData.hasTwelveCharacters = data.password.length >= 12;
|
|
||||||
this.setState(stateData);
|
|
||||||
};
|
|
||||||
|
|
||||||
doSubmit = async (buttonName: string) => {
|
|
||||||
const { emailUserAction } = this.props;
|
|
||||||
const { password } = this.state.data;
|
|
||||||
|
|
||||||
const action: IEmailUserAction = {
|
|
||||||
email: emailUserAction.email,
|
|
||||||
token: emailUserAction.token,
|
|
||||||
password: password,
|
|
||||||
emailActionType: emailUserAction.emailActionType,
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
const callResult = await authentication.completeEmailAction(action);
|
|
||||||
if (callResult === 1) {
|
|
||||||
let data = { ...this.state.data };
|
|
||||||
data.passwordChanged = true;
|
|
||||||
this.setState({ data });
|
|
||||||
setTimeout(function () {
|
|
||||||
window.location.replace('/login');
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ex: any) {
|
|
||||||
this.handleGeneralError(ex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { passwordChanged, password, confirmPassword } = this.state.data;
|
|
||||||
const { hasNumber, hasLowercaseLetter, hasSpecialCharacter, hasUppercaseLetter, hasTwelveCharacters, passwordMaxLenght } = this.state;
|
|
||||||
const isFormValid = password !== "" && password === confirmPassword && hasNumber && hasLowercaseLetter && hasSpecialCharacter && hasUppercaseLetter && hasTwelveCharacters;
|
|
||||||
if (passwordChanged) {
|
|
||||||
return <div className="alert alert-info">Your password has been reset. Please contact your admin if this wasn't you.</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
{this.renderError("_general")}
|
|
||||||
{this.renderInputWithChangeEvent("password", "", InputType.password, undefined, this.handlePasswordChange, undefined, this.labelPassword, passwordMaxLenght)}
|
|
||||||
<div className={hasTwelveCharacters ? "checked" : "unchecked"}>Password requires a minimum of 12 characters containing a combination of:</div>
|
|
||||||
<ul>
|
|
||||||
<li className={hasSpecialCharacter ? "checked" : ""}>At least 1 symbol</li>
|
|
||||||
<li className={hasNumber ? "checked" : ""}>At least 1 number</li>
|
|
||||||
<li className={hasLowercaseLetter ? "checked" : ""}>At least 1 lowercase letter</li>
|
|
||||||
<li className={hasUppercaseLetter ? "checked" : ""}>At least 1 uppercase letter</li>
|
|
||||||
</ul>
|
|
||||||
{this.renderInput("confirmPassword", "", InputType.password, undefined, undefined, this.labelConfirmPassword, passwordMaxLenght)}
|
|
||||||
{this.renderButton(this.labelChangePassword, "save", undefined, undefined, isFormValid)}
|
|
||||||
</form>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (Object.keys(newErrors).length > 0) {
|
||||||
|
setErrors(newErrors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrors({});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const action: IEmailUserAction = {
|
||||||
|
email: emailUserAction.email,
|
||||||
|
token: emailUserAction.token,
|
||||||
|
password: password,
|
||||||
|
emailActionType: emailUserAction.emailActionType,
|
||||||
|
};
|
||||||
|
|
||||||
|
const callResult = await authentication.completeEmailAction(action);
|
||||||
|
if (callResult === 1) {
|
||||||
|
setPasswordChanged(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.replace("/login");
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
} catch (ex: any) {
|
||||||
|
setGeneralError(ex?.message || t("AnErrorOccurred"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isFormValid =
|
||||||
|
password !== "" &&
|
||||||
|
password === confirmPassword &&
|
||||||
|
hasNumber &&
|
||||||
|
hasLowercaseLetter &&
|
||||||
|
hasSpecialCharacter &&
|
||||||
|
hasUppercaseLetter &&
|
||||||
|
hasTwelveCharacters;
|
||||||
|
|
||||||
|
if (passwordChanged) {
|
||||||
|
return (
|
||||||
|
<div className="alert alert-info">
|
||||||
|
Your password has been reset. Please contact your admin if this wasn't
|
||||||
|
you.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{generalError && <ErrorBlock error={generalError} />}
|
||||||
|
<Input
|
||||||
|
name="password"
|
||||||
|
label={LABEL_PASSWORD}
|
||||||
|
type={InputType.password}
|
||||||
|
value={password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
maxLength={PASSWORD_MAX_LENGTH}
|
||||||
|
error={errors.password}
|
||||||
|
/>
|
||||||
|
<div className={hasTwelveCharacters ? "checked" : "unchecked"}>
|
||||||
|
Password requires a minimum of 12 characters containing a combination
|
||||||
|
of:
|
||||||
|
</div>
|
||||||
|
<ul>
|
||||||
|
<li className={hasSpecialCharacter ? "checked" : ""}>
|
||||||
|
At least 1 symbol
|
||||||
|
</li>
|
||||||
|
<li className={hasNumber ? "checked" : ""}>At least 1 number</li>
|
||||||
|
<li className={hasLowercaseLetter ? "checked" : ""}>
|
||||||
|
At least 1 lowercase letter
|
||||||
|
</li>
|
||||||
|
<li className={hasUppercaseLetter ? "checked" : ""}>
|
||||||
|
At least 1 uppercase letter
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<Input
|
||||||
|
name="confirmPassword"
|
||||||
|
label={LABEL_CONFIRM_PASSWORD}
|
||||||
|
type={InputType.password}
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={handleConfirmPasswordChange}
|
||||||
|
maxLength={PASSWORD_MAX_LENGTH}
|
||||||
|
error={errors.confirmPassword}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
buttonType={ButtonType.primary}
|
||||||
|
disabled={!isFormValid}
|
||||||
|
onClick={() => {}}
|
||||||
|
>
|
||||||
|
{LABEL_CHANGE_PASSWORD}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default EmailUserActionPasswordReset;
|
export default EmailUserActionPasswordReset;
|
||||||
|
|||||||
@ -1,69 +1,86 @@
|
|||||||
import Joi from "joi";
|
import React, { useState } from "react";
|
||||||
import Form, { FormData, FormState } from "../../../components/common/Form";
|
import { FormData } from "../../../components/common/Form";
|
||||||
import authentication from "../services/authenticationService";
|
import authentication from "../services/authenticationService";
|
||||||
|
import Input, { InputType } from "../../../components/common/Input";
|
||||||
|
import Button, { ButtonType } from "../../../components/common/Button";
|
||||||
|
import ErrorBlock from "../../../components/common/ErrorBlock";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Namespaces } from "../../../i18n/i18n";
|
||||||
|
|
||||||
export interface ForgotPasswordData extends FormData {
|
export interface ForgotPasswordData extends FormData {
|
||||||
username: string;
|
username: string;
|
||||||
emailSent: boolean;
|
emailSent: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ForgotPasswordtate extends FormState {
|
const ForgotPassword: React.FC = () => {
|
||||||
data: ForgotPasswordData;
|
const { t } = useTranslation<typeof Namespaces.Common>();
|
||||||
}
|
const [username, setUsername] = useState("");
|
||||||
|
const [emailSent, setEmailSent] = useState(false);
|
||||||
|
const [generalError, setGeneralError] = useState("");
|
||||||
|
const [errors, setErrors] = useState<{ [key: string]: string }>({});
|
||||||
|
|
||||||
class ForgotPassword extends Form<any, any, ForgotPasswordtate> {
|
const validateEmail = (email: string): boolean => {
|
||||||
state = {
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||||
loaded: true,
|
return emailRegex.test(email);
|
||||||
data: { username: "", emailSent: false },
|
};
|
||||||
errors: {},
|
|
||||||
};
|
|
||||||
|
|
||||||
schema = {
|
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
username: Joi.string()
|
e.preventDefault();
|
||||||
.required()
|
const newErrors: { [key: string]: string } = {};
|
||||||
.email({ tlds: { allow: false } })
|
|
||||||
.label("Username"),
|
|
||||||
emailSent: Joi.boolean().required(),
|
|
||||||
};
|
|
||||||
|
|
||||||
doSubmit = async (buttonName : string) => {
|
// Validation
|
||||||
try {
|
if (!username) {
|
||||||
let { data } = this.state;
|
newErrors.username = t("UsernameIsRequired");
|
||||||
|
} else if (!validateEmail(username)) {
|
||||||
const response = await authentication.forgotPassword(data.username);
|
newErrors.username = t("UsernameMustBeValidEmail");
|
||||||
if (response) {
|
|
||||||
data.emailSent = true;
|
|
||||||
this.setState({ data });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch(ex: any) {
|
|
||||||
this.handleGeneralError(ex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { emailSent } = this.state.data;
|
|
||||||
|
|
||||||
let content = (
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
{this.renderError("_general")}
|
|
||||||
{this.renderInput("username", "Username")}
|
|
||||||
{this.renderButton("Reset password")}
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
if (emailSent) {
|
|
||||||
content = <div>An email with a password reset link has been sent.</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<div className="forgottenLink">
|
|
||||||
<h1>Forgot password</h1>
|
|
||||||
</div>
|
|
||||||
{content}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
if (Object.keys(newErrors).length > 0) {
|
||||||
|
setErrors(newErrors);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setErrors({});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await authentication.forgotPassword(username);
|
||||||
|
if (response) {
|
||||||
|
setEmailSent(true);
|
||||||
|
}
|
||||||
|
} catch (ex: any) {
|
||||||
|
setGeneralError(ex?.message || t("AnErrorOccurred"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = (
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
{generalError && <ErrorBlock error={generalError} />}
|
||||||
|
<Input
|
||||||
|
type={InputType.text}
|
||||||
|
name="username"
|
||||||
|
label={t("Username")}
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.currentTarget.value)}
|
||||||
|
error={errors.username}
|
||||||
|
/>
|
||||||
|
<Button buttonType={ButtonType.primary} onClick={() => {}}>
|
||||||
|
{t("ResetPassword")}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
|
||||||
|
if (emailSent) {
|
||||||
|
content = <div>{t("AnEmailWithPasswordResetLinkHasBeenSent")}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="forgottenLink">
|
||||||
|
<h1>Forgot password</h1>
|
||||||
|
</div>
|
||||||
|
{content}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default ForgotPassword;
|
export default ForgotPassword;
|
||||||
|
|||||||
@ -1,49 +1,45 @@
|
|||||||
import { IconDefinition } from "@fortawesome/pro-thin-svg-icons";
|
import { IconDefinition } from "@fortawesome/pro-thin-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import React, { Component } from "react";
|
import React from "react";
|
||||||
import { Link } from "react-router-dom";
|
import { Link, useLocation } from "react-router-dom";
|
||||||
import withRouter, { RouterProps } from "../../../utils/withRouter";
|
|
||||||
|
|
||||||
interface LeftMenuItemProps extends RouterProps {
|
interface LeftMenuItemProps {
|
||||||
to : string;
|
to: string;
|
||||||
icon? : IconDefinition;
|
icon?: IconDefinition;
|
||||||
label : string;
|
label: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LOCLeftMenuItem extends Component<LeftMenuItemProps> {
|
const LeftMenuItem: React.FC<LeftMenuItemProps> = ({ to, icon, label }) => {
|
||||||
isSelected = ():boolean => {
|
const location = useLocation();
|
||||||
const { to } = this.props;
|
|
||||||
const { pathname } = this.props.router.location;
|
|
||||||
let isSelected : boolean = false;
|
|
||||||
if (to === '/' ? (pathname === to) : pathname.toLowerCase().startsWith(to)){
|
|
||||||
isSelected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isSelected;
|
const isSelected = (): boolean => {
|
||||||
}
|
const pathname = location.pathname;
|
||||||
|
return to === "/" ? pathname === to : pathname.toLowerCase().startsWith(to);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
let className = "";
|
||||||
const { to, icon, label } = this.props;
|
|
||||||
|
|
||||||
let className = "";
|
if (isSelected()) {
|
||||||
|
className += " leftMenuSelected";
|
||||||
|
}
|
||||||
|
|
||||||
if (this.isSelected()) {
|
if (icon) {
|
||||||
className += " leftMenuSelected";
|
return (
|
||||||
}
|
<div className="LeftMenuItem">
|
||||||
|
<Link className={className} to={to}>
|
||||||
|
<FontAwesomeIcon className="leftMenuItemIcon" icon={icon} />
|
||||||
|
<div className="leftMenuItemLabel">{label}</div>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if ( icon) {
|
return (
|
||||||
return (
|
<Link className={className} to={to}>
|
||||||
<div className="LeftMenuItem"><Link className={className} to={to} ><FontAwesomeIcon className="leftMenuItemIcon" icon={icon}/><div className="leftMenuItemLabel">{label}</div></Link></div>
|
{label}
|
||||||
);
|
</Link>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
|
||||||
<Link className={className} to={to} >{label}</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const LeftMenuItem = withRouter(LOCLeftMenuItem);
|
|
||||||
|
|
||||||
export default LeftMenuItem;
|
export default LeftMenuItem;
|
||||||
export { LOCLeftMenuItem };
|
export { LeftMenuItem as LOCLeftMenuItem };
|
||||||
|
|||||||
@ -3,71 +3,74 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import withRouter, { RouterProps } from "../../../utils/withRouter";
|
import withRouter, { RouterProps } from "../../../utils/withRouter";
|
||||||
|
|
||||||
interface LeftMenuSubMenuProps extends RouterProps{
|
interface LeftMenuSubMenuProps extends RouterProps {
|
||||||
icon : IconDefinition;
|
icon: IconDefinition;
|
||||||
label : string;
|
label: string;
|
||||||
openMenu? : LOCLeftMenuSubMenu;
|
openMenu?: LOCLeftMenuSubMenu;
|
||||||
children : (false | JSX.Element)[];
|
children: (false | JSX.Element)[];
|
||||||
onClick? : ( menuItem : LOCLeftMenuSubMenu ) => void;
|
onClick?: (menuItem: LOCLeftMenuSubMenu) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LeftMenuSubMenuState {
|
interface LeftMenuSubMenuState {}
|
||||||
|
|
||||||
|
class LOCLeftMenuSubMenu extends React.Component<
|
||||||
|
LeftMenuSubMenuProps,
|
||||||
|
LeftMenuSubMenuState
|
||||||
|
> {
|
||||||
|
state = {};
|
||||||
|
|
||||||
|
handleClick = (): void => {
|
||||||
|
const { onClick } = this.props;
|
||||||
|
|
||||||
|
if (onClick !== undefined) onClick(this);
|
||||||
|
};
|
||||||
|
|
||||||
|
isChildSelected = (child: JSX.Element): boolean => {
|
||||||
|
const { to } = child.props;
|
||||||
|
const { pathname } = this.props.router.location;
|
||||||
|
let isSelected: boolean = false;
|
||||||
|
if (to === "/" ? pathname === to : pathname.toLowerCase().startsWith(to)) {
|
||||||
|
isSelected = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isSelected;
|
||||||
|
};
|
||||||
|
|
||||||
|
isAnyChildSelected = (): boolean => {
|
||||||
|
const { children } = this.props;
|
||||||
|
|
||||||
|
let childIsSelected = false;
|
||||||
|
children.forEach((child) => {
|
||||||
|
if (child === false) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isChildSelected(child)) childIsSelected = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
return childIsSelected;
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { icon, label, openMenu } = this.props;
|
||||||
|
|
||||||
|
const selected = this === openMenu || this.isAnyChildSelected();
|
||||||
|
|
||||||
|
let className = "LeftMenuItem leftMenuSubMenu";
|
||||||
|
|
||||||
|
if (selected) {
|
||||||
|
className += " leftMenuSubMenuOpen";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={className} onClick={this.handleClick}>
|
||||||
|
<FontAwesomeIcon className="leftMenuItemIcon" icon={icon} />
|
||||||
|
<div className="leftMenuItemLabel">{label}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LOCLeftMenuSubMenu extends React.Component<LeftMenuSubMenuProps, LeftMenuSubMenuState> {
|
|
||||||
state = { }
|
|
||||||
|
|
||||||
handleClick = (): void =>
|
|
||||||
{
|
|
||||||
const { onClick } = this.props;
|
|
||||||
|
|
||||||
if (onClick !== undefined)
|
|
||||||
onClick(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
isChildSelected = (child : JSX.Element):boolean => {
|
|
||||||
const { to } = child.props;
|
|
||||||
const { pathname } = this.props.router.location;
|
|
||||||
let isSelected : boolean = false;
|
|
||||||
if (to === '/' ? (pathname === to) : pathname.toLowerCase().startsWith(to)){
|
|
||||||
isSelected = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return isSelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
isAnyChildSelected = ():boolean => {
|
|
||||||
const { children } = this.props;
|
|
||||||
|
|
||||||
let childIsSelected = false;
|
|
||||||
children.forEach(child => {
|
|
||||||
if (child === false){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.isChildSelected(child))
|
|
||||||
childIsSelected = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
return childIsSelected;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { icon, label, openMenu } = this.props;
|
|
||||||
|
|
||||||
const selected = this === openMenu || this.isAnyChildSelected();
|
|
||||||
|
|
||||||
let className = "LeftMenuItem leftMenuSubMenu";
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
className += " leftMenuSubMenuOpen";
|
|
||||||
}
|
|
||||||
|
|
||||||
return ( <div className={className} onClick={this.handleClick}><FontAwesomeIcon className="leftMenuItemIcon" icon={icon}/><div className="leftMenuItemLabel">{label}</div></div> );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const LeftMenuSubMenu = withRouter(LOCLeftMenuSubMenu);
|
const LeftMenuSubMenu = withRouter(LOCLeftMenuSubMenu);
|
||||||
export default LeftMenuSubMenu;
|
export default LeftMenuSubMenu;
|
||||||
export {LOCLeftMenuSubMenu}
|
export { LOCLeftMenuSubMenu };
|
||||||
|
|||||||
@ -8,159 +8,223 @@ import { ButtonType } from "../../../components/common/Button";
|
|||||||
//import '../../../Sass/login.scss';
|
//import '../../../Sass/login.scss';
|
||||||
|
|
||||||
export interface LoginFormStateData extends FormData {
|
export interface LoginFormStateData extends FormData {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
tfaNeeded: boolean;
|
tfaNeeded: boolean;
|
||||||
requestTfaRemoval: boolean;
|
requestTfaRemoval: boolean;
|
||||||
securityCode: string;
|
securityCode: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginFormState extends FormState {
|
export interface LoginFormState extends FormState {
|
||||||
passwordMaxLength: number,
|
passwordMaxLength: number;
|
||||||
isInNextStage: boolean,
|
isInNextStage: boolean;
|
||||||
emailSent: boolean,
|
emailSent: boolean;
|
||||||
data: LoginFormStateData;
|
data: LoginFormStateData;
|
||||||
}
|
}
|
||||||
|
|
||||||
class LoginForm extends Form<any, any, LoginFormState> {
|
class LoginForm extends Form<any, any, LoginFormState> {
|
||||||
state = {
|
state = {
|
||||||
loaded: true,
|
loaded: true,
|
||||||
passwordMaxLength: 255,
|
passwordMaxLength: 255,
|
||||||
isInNextStage: false,
|
isInNextStage: false,
|
||||||
emailSent: false,
|
emailSent: false,
|
||||||
data: {
|
data: {
|
||||||
username: "",
|
username: "",
|
||||||
password: "",
|
password: "",
|
||||||
tfaNeeded: false,
|
tfaNeeded: false,
|
||||||
requestTfaRemoval: false,
|
requestTfaRemoval: false,
|
||||||
securityCode: "",
|
securityCode: "",
|
||||||
},
|
},
|
||||||
errors: {},
|
errors: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
schema = {
|
schema = {
|
||||||
username: Joi.string()
|
username: Joi.string()
|
||||||
.required()
|
.required()
|
||||||
.email({ tlds: { allow: false } })
|
.email({ tlds: { allow: false } })
|
||||||
.label("Email"),
|
.label("Email"),
|
||||||
password: Joi.string().required().label("Password"),
|
password: Joi.string().required().label("Password"),
|
||||||
tfaNeeded: Joi.boolean().required(),
|
tfaNeeded: Joi.boolean().required(),
|
||||||
requestTfaRemoval: Joi.boolean().required(),
|
requestTfaRemoval: Joi.boolean().required(),
|
||||||
securityCode: Joi.string().allow("").label("Authenticate"),
|
securityCode: Joi.string().allow("").label("Authenticate"),
|
||||||
};
|
};
|
||||||
|
|
||||||
doSubmit = async (buttonName : string) => {
|
doSubmit = async (buttonName: string) => {
|
||||||
const { data } = this.state;
|
const { data } = this.state;
|
||||||
await this.performLogin(data);
|
await this.performLogin(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleNextClick = async (event: React.MouseEvent) => {
|
handleNextClick = async (event: React.MouseEvent) => {
|
||||||
const data: LoginFormStateData = { ...this.state.data };
|
const data: LoginFormStateData = { ...this.state.data };
|
||||||
var validationResult = this.schema.username.validate(data.username);
|
var validationResult = this.schema.username.validate(data.username);
|
||||||
if (validationResult.error === undefined) {
|
if (validationResult.error === undefined) {
|
||||||
const stateData = this.state;
|
const stateData = this.state;
|
||||||
stateData.isInNextStage = true;
|
stateData.isInNextStage = true;
|
||||||
this.setState(stateData);
|
this.setState(stateData);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
handleForgetPassword = async () => {
|
handleForgetPassword = async () => {
|
||||||
try {
|
try {
|
||||||
const stateData = this.state;
|
const stateData = this.state;
|
||||||
await authentication.forgotPassword(stateData.data.username);
|
await authentication.forgotPassword(stateData.data.username);
|
||||||
stateData.emailSent = true;
|
stateData.emailSent = true;
|
||||||
stateData.data.username = "";
|
stateData.data.username = "";
|
||||||
stateData.data.password = "";
|
stateData.data.password = "";
|
||||||
this.setState(stateData);
|
this.setState(stateData);
|
||||||
}
|
} catch (ex: any) {
|
||||||
catch (ex: any) {
|
this.handleGeneralError(ex);
|
||||||
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() {
|
authenticationWorkAround = async () => {
|
||||||
window.location.replace("/login");
|
const data: LoginFormStateData = { ...this.state.data };
|
||||||
|
data.requestTfaRemoval = true;
|
||||||
const { tfaNeeded, requestTfaRemoval} = this.state.data;
|
|
||||||
const { isInNextStage, data, emailSent, passwordMaxLength } = this.state;
|
|
||||||
const result = this.schema.username.validate(data.username);
|
|
||||||
const validEmail = (result.error === undefined) ? true : false;
|
|
||||||
|
|
||||||
if (authentication.getCurrentUser()) return <Navigate to="/" />;
|
await this.performLogin(data);
|
||||||
|
|
||||||
const requestTfaRemovalPanel = <div>An email has been sent to you so that you can regain control of your account.</div>;
|
this.setState({ data });
|
||||||
|
};
|
||||||
|
|
||||||
const loginPanel = (
|
private async performLogin(data: LoginFormStateData) {
|
||||||
<><form onSubmit={this.handleSubmit}>
|
try {
|
||||||
{this.renderInput("username", "", InputType.text, isInNextStage, undefined, "Email", undefined, undefined,"username")}
|
let result = await authentication.login(
|
||||||
{this.renderInput("password", "", InputType.password, emailSent, undefined, "Password", passwordMaxLength, isInNextStage, "current-password")}
|
data.username,
|
||||||
{!isInNextStage && this.renderButton("Next", "login", this.handleNextClick, "next", validEmail, ButtonType.primary, true)}
|
data.password,
|
||||||
{isInNextStage && <div className="clickables">
|
data.securityCode,
|
||||||
{this.renderButton("Login", "login", undefined, "login", !emailSent)}
|
data.requestTfaRemoval,
|
||||||
</div>
|
);
|
||||||
}
|
|
||||||
</form>
|
|
||||||
{isInNextStage && <div className="forgottenLink">
|
|
||||||
{this.renderButton("Forgotten Password", "forgot-password", this.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>}
|
|
||||||
{this.renderError("_general")}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const tfaPanel = (
|
switch (result) {
|
||||||
<form onSubmit={this.handleSubmit}>
|
case 1: //requires tfa
|
||||||
{this.renderError("_general")}
|
const { data } = this.state;
|
||||||
{this.renderInput("securityCode", "Authenticate")}
|
|
||||||
{this.renderButton("Authenticate")}
|
|
||||||
<Link to="#" onClick={this.authenticationWorkAround}>
|
|
||||||
My Authenticator is not working
|
|
||||||
</Link>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
if (data.tfaNeeded === true) {
|
||||||
<div>
|
//TFA removal Request accepted.
|
||||||
{requestTfaRemoval ? requestTfaRemovalPanel : tfaNeeded ? tfaPanel : loginPanel}
|
} 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");
|
||||||
|
|
||||||
|
const { tfaNeeded, requestTfaRemoval } = this.state.data;
|
||||||
|
const { isInNextStage, data, emailSent, passwordMaxLength } = this.state;
|
||||||
|
const result = this.schema.username.validate(data.username);
|
||||||
|
const validEmail = result.error === undefined ? true : false;
|
||||||
|
|
||||||
|
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={this.handleSubmit}>
|
||||||
|
{this.renderInput(
|
||||||
|
"username",
|
||||||
|
"",
|
||||||
|
InputType.text,
|
||||||
|
isInNextStage,
|
||||||
|
undefined,
|
||||||
|
"Email",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
"username",
|
||||||
|
)}
|
||||||
|
{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>
|
</div>
|
||||||
);
|
)}
|
||||||
}
|
</form>
|
||||||
|
{isInNextStage && (
|
||||||
|
<div className="forgottenLink">
|
||||||
|
{this.renderButton(
|
||||||
|
"Forgotten Password",
|
||||||
|
"forgot-password",
|
||||||
|
this.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>
|
||||||
|
)}
|
||||||
|
{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>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{requestTfaRemoval
|
||||||
|
? requestTfaRemovalPanel
|
||||||
|
: tfaNeeded
|
||||||
|
? tfaPanel
|
||||||
|
: loginPanel}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default LoginForm;
|
export default LoginForm;
|
||||||
|
|||||||
@ -1,20 +1,20 @@
|
|||||||
import * as React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
import authentication from "../services/authenticationService";
|
import authentication from "../services/authenticationService";
|
||||||
|
|
||||||
class Logout extends React.Component {
|
const Logout: React.FC = () => {
|
||||||
componentDidMount() {
|
const { t } = useTranslation();
|
||||||
authentication.logout();
|
|
||||||
if (window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN) {
|
|
||||||
window.location.href = "/account/logout"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
window.location.href = "/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
useEffect(() => {
|
||||||
return <div>Logging out</div>;
|
authentication.logout();
|
||||||
|
if (window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN) {
|
||||||
|
window.location.href = "/account/logout";
|
||||||
|
} else {
|
||||||
|
window.location.href = "/";
|
||||||
}
|
}
|
||||||
}
|
}, []);
|
||||||
|
|
||||||
|
return <div>{t("LoggingOut")}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
export default Logout;
|
export default Logout;
|
||||||
|
|||||||
@ -1,23 +1,23 @@
|
|||||||
import * as React from "react";
|
import React from "react";
|
||||||
import TopMenu from "./TopMenu";
|
import TopMenu from "./TopMenu";
|
||||||
import LeftMenu from "./LeftMenu";
|
import LeftMenu from "./LeftMenu";
|
||||||
|
|
||||||
import "../../../Sass/_frame.scss";
|
import "../../../Sass/_frame.scss";
|
||||||
|
|
||||||
type MainFrameProps = {
|
type MainFrameProps = {
|
||||||
title?: string | undefined | null;
|
title?: string | null;
|
||||||
children?: React.ReactNode; // 👈️ type children
|
children?: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Mainframe = (props: MainFrameProps): JSX.Element => {
|
const Mainframe: React.FC<MainFrameProps> = ({ title, children }) => {
|
||||||
return (
|
return (
|
||||||
<div className="frame">
|
<div className="frame">
|
||||||
<TopMenu title={props.title ? props.title : undefined} />
|
<TopMenu title={title} />
|
||||||
<div className="frame-row">
|
<div className="frame-row">
|
||||||
<div className="frame-leftMenu">
|
<div className="frame-leftMenu">
|
||||||
<LeftMenu />
|
<LeftMenu />
|
||||||
</div>
|
</div>
|
||||||
<div className="frame-workArea">{props.children}</div>
|
<div className="frame-workArea">{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,7 +1,10 @@
|
|||||||
import * as React from "react";
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function NotFound() {
|
const NotFound: React.FC = () => {
|
||||||
return <h1>Not found</h1>;
|
const { t } = useTranslation();
|
||||||
}
|
|
||||||
|
return <h1>{t("NotFound")}</h1>;
|
||||||
|
};
|
||||||
|
|
||||||
export default NotFound;
|
export default NotFound;
|
||||||
|
|||||||
@ -1,49 +0,0 @@
|
|||||||
import * as React from "react";
|
|
||||||
|
|
||||||
export interface SwitchProps {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Switch extends React.PureComponent<SwitchProps> {
|
|
||||||
render() {
|
|
||||||
const children = React.Children.toArray(this.props.children);
|
|
||||||
|
|
||||||
let caseComponent: any = children.filter((c:any) => {
|
|
||||||
return c.type === Case && c.props.condition === true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!caseComponent || caseComponent.length === 0) {
|
|
||||||
caseComponent = children.filter((c: any) => c.type === Else);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
{ caseComponent }
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CaseProps {
|
|
||||||
condition: boolean;
|
|
||||||
children: React.ReactNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Case extends React.PureComponent<CaseProps> {
|
|
||||||
render() {
|
|
||||||
const { condition, children } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<React.Fragment>
|
|
||||||
{ condition ? children : null }
|
|
||||||
</React.Fragment>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Else extends React.PureComponent<SwitchProps>
|
|
||||||
{
|
|
||||||
render() {
|
|
||||||
return this.props.children;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -12,7 +12,7 @@ import { getCurrentUser } from "../services/authenticationService";
|
|||||||
import { LanguageSelectorMenuItem } from "./LanguageSelector";
|
import { LanguageSelectorMenuItem } from "./LanguageSelector";
|
||||||
|
|
||||||
export interface TopMenuProps {
|
export interface TopMenuProps {
|
||||||
title?: string;
|
title: string | undefined | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function TopMenu(props: TopMenuProps) {
|
function TopMenu(props: TopMenuProps) {
|
||||||
@ -21,7 +21,7 @@ function TopMenu(props: TopMenuProps) {
|
|||||||
return (
|
return (
|
||||||
<Navbar className="navbar bg-body-tertiary px-4 Header">
|
<Navbar className="navbar bg-body-tertiary px-4 Header">
|
||||||
<Navbar.Brand href="/">
|
<Navbar.Brand href="/">
|
||||||
<Logo alt="esuite logo" />
|
<Logo />
|
||||||
</Navbar.Brand>
|
</Navbar.Brand>
|
||||||
<div className="navbar-left">{props.title}</div>
|
<div className="navbar-left">{props.title}</div>
|
||||||
<div className="navbar-right">
|
<div className="navbar-right">
|
||||||
|
|||||||
@ -5,34 +5,24 @@ import "../../../Sass/login.scss";
|
|||||||
import Logo from "../../../img/logo";
|
import Logo from "../../../img/logo";
|
||||||
|
|
||||||
interface LoginFrameProps {
|
interface LoginFrameProps {
|
||||||
children?: JSX.Element
|
children?: JSX.Element;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoginFrameState {
|
const LoginFrame: React.FC<LoginFrameProps> = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div className="container-fluid vh-100">
|
||||||
|
<div className="col-md-2">
|
||||||
|
<div className="loginFormContainer">
|
||||||
|
<div className="col-12 logo">
|
||||||
|
<Logo height="120px" width="120px" />
|
||||||
|
</div>
|
||||||
|
<div className="col-12">{children}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
}
|
<div className="col-md-8"></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
class LoginFrame extends React.Component<LoginFrameProps, LoginFrameState> {
|
export default LoginFrame;
|
||||||
|
|
||||||
render() {
|
|
||||||
const { children } = this.props;
|
|
||||||
|
|
||||||
return (<div className="container-fluid vh-100">
|
|
||||||
<div className="col-md-2">
|
|
||||||
<div className="loginFormContainer">
|
|
||||||
<div className="col-12 logo">
|
|
||||||
<Logo alt="esuite logo" height="120px" width="120px" />
|
|
||||||
</div>
|
|
||||||
<div className="col-12">
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-md-8"></div>
|
|
||||||
|
|
||||||
</div>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LoginFrame;
|
|
||||||
|
|||||||
@ -1,13 +1,23 @@
|
|||||||
import * as React from "react";
|
import React from "react";
|
||||||
|
|
||||||
function EnvPage() {
|
const EnvPage: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p>This is the Environment</p>
|
<p>This is the Environment</p>
|
||||||
<br></br>
|
<br />
|
||||||
<p>window.__RUNTIME_CONFIG__.API_URL = {window.__RUNTIME_CONFIG__.API_URL}</p>
|
<p>
|
||||||
</>
|
window.__RUNTIME_CONFIG__.NODE_ENV ={" "}
|
||||||
);
|
{window.__RUNTIME_CONFIG__.NODE_ENV}
|
||||||
}
|
</p>
|
||||||
|
<p>
|
||||||
|
window.__RUNTIME_CONFIG__.API_URL = {window.__RUNTIME_CONFIG__.API_URL}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ={" "}
|
||||||
|
{window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ? "true" : "false"}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default EnvPage;
|
export default EnvPage;
|
||||||
|
|||||||
@ -1,20 +1,24 @@
|
|||||||
import * as React from "react";
|
import React from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
function HomePage() {
|
const HomePage: React.FC = () => {
|
||||||
const redirect = ()=> {
|
const { t } = useTranslation();
|
||||||
window.location.href = '/organisations'
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const redirect = () => {
|
||||||
<div className="fluid-container">
|
window.location.href = "/organisations";
|
||||||
<h3>Applications</h3>
|
};
|
||||||
<div className="e-printWidget" onClick={redirect}>
|
|
||||||
<div className="e-print">
|
return (
|
||||||
<div className="thumbnail alert"></div><div className="label">E-print</div>
|
<div className="fluid-container">
|
||||||
</div>
|
<h3>{t("Applications")}</h3>
|
||||||
</div>
|
<div className="e-printWidget" onClick={redirect}>
|
||||||
|
<div className="e-print">
|
||||||
|
<div className="thumbnail alert"></div>
|
||||||
|
<div className="label">{t("e-print")}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default HomePage;
|
export default HomePage;
|
||||||
|
|||||||
@ -7,351 +7,476 @@ import { InputType } from "../../../components/common/Input";
|
|||||||
import { FormState } from "../../../components/common/Form";
|
import { FormState } from "../../../components/common/Form";
|
||||||
import withRouter from "../../../utils/withRouter";
|
import withRouter from "../../../utils/withRouter";
|
||||||
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
|
import { MakeGeneralIdRef } from "../../../utils/GeneralIdRef";
|
||||||
import customFieldsService, { numberParams, textParams } from "./services/customFieldsService";
|
import customFieldsService, {
|
||||||
|
numberParams,
|
||||||
|
textParams,
|
||||||
|
} from "./services/customFieldsService";
|
||||||
import Option from "../../../components/common/option";
|
import Option from "../../../components/common/option";
|
||||||
import { GeneralIdRef } from "./../../../utils/GeneralIdRef";
|
import { GeneralIdRef } from "./../../../utils/GeneralIdRef";
|
||||||
import { Case, Else, Switch } from "../../frame/components/Switch";
|
import {
|
||||||
import { CustomFieldValue, SystemGlossaries } from "../glossary/services/glossaryService";
|
CustomFieldValue,
|
||||||
|
SystemGlossaries,
|
||||||
|
} from "../glossary/services/glossaryService";
|
||||||
import Loading from "../../../components/common/Loading";
|
import Loading from "../../../components/common/Loading";
|
||||||
|
|
||||||
interface CustomFieldDetailsState extends FormState {
|
interface CustomFieldDetailsState extends FormState {
|
||||||
data: {
|
data: {
|
||||||
name: string;
|
name: string;
|
||||||
fieldType: string;
|
fieldType: string;
|
||||||
multiLine: boolean;
|
multiLine: boolean;
|
||||||
defaultValue: string;
|
defaultValue: string;
|
||||||
minEntries: number;
|
minEntries: number;
|
||||||
maxEntries: string | number | undefined;
|
maxEntries: string | number | undefined;
|
||||||
refElementId: CustomFieldValue[] | GeneralIdRef | undefined;
|
refElementId: CustomFieldValue[] | GeneralIdRef | undefined;
|
||||||
minValue: number | undefined;
|
minValue: number | undefined;
|
||||||
maxValue: number | undefined;
|
maxValue: number | undefined;
|
||||||
step: number | undefined;
|
step: number | undefined;
|
||||||
required: boolean;
|
required: boolean;
|
||||||
};
|
};
|
||||||
redirect: string;
|
redirect: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
|
class CustomFieldDetails extends Form<any, any, CustomFieldDetailsState> {
|
||||||
state: CustomFieldDetailsState = {
|
state: CustomFieldDetailsState = {
|
||||||
loaded: false,
|
loaded: false,
|
||||||
data: {
|
data: {
|
||||||
name: "",
|
name: "",
|
||||||
fieldType: "Text",
|
fieldType: "Text",
|
||||||
defaultValue: "",
|
defaultValue: "",
|
||||||
multiLine: false,
|
multiLine: false,
|
||||||
minEntries: 0,
|
minEntries: 0,
|
||||||
maxEntries: 1,
|
maxEntries: 1,
|
||||||
refElementId: undefined,
|
refElementId: undefined,
|
||||||
minValue: undefined,
|
minValue: undefined,
|
||||||
maxValue: undefined,
|
maxValue: undefined,
|
||||||
step: undefined,
|
step: undefined,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
errors: {},
|
errors: {},
|
||||||
redirect: "",
|
redirect: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
labelName = "Name";
|
labelName = "Name";
|
||||||
labelFieldType = "Field Type";
|
labelFieldType = "Field Type";
|
||||||
labelMultiLine = "Multi-line";
|
labelMultiLine = "Multi-line";
|
||||||
labelDefaultValue = "Default Value";
|
labelDefaultValue = "Default Value";
|
||||||
labelMinValue = "Minimum Value";
|
labelMinValue = "Minimum Value";
|
||||||
labelMaxValue = "Maximum Value";
|
labelMaxValue = "Maximum Value";
|
||||||
labelStep = "Step";
|
labelStep = "Step";
|
||||||
labelRequired = "Required";
|
labelRequired = "Required";
|
||||||
labelMinEntries = "Min Entries";
|
labelMinEntries = "Min Entries";
|
||||||
labelMaxEntries = "Max Entries (empty=unlimited)";
|
labelMaxEntries = "Max Entries (empty=unlimited)";
|
||||||
labelRefElementId = "Sequence/Form/Glossary";
|
labelRefElementId = "Sequence/Form/Glossary";
|
||||||
|
|
||||||
labelApply = "Save";
|
labelApply = "Save";
|
||||||
labelSave = "Save and close";
|
labelSave = "Save and close";
|
||||||
|
|
||||||
schema = {
|
schema = {
|
||||||
name: Joi.string().required().max(450).label(this.labelName),
|
name: Joi.string().required().max(450).label(this.labelName),
|
||||||
fieldType: Joi.string().required().label(this.labelFieldType),
|
fieldType: Joi.string().required().label(this.labelFieldType),
|
||||||
multiLine: Joi.boolean().label(this.labelMultiLine),
|
multiLine: Joi.boolean().label(this.labelMultiLine),
|
||||||
minEntries: Joi.number().min(0).label(this.labelMinEntries),
|
minEntries: Joi.number().min(0).label(this.labelMinEntries),
|
||||||
maxEntries: Joi.number().empty("").label(this.labelMaxEntries),
|
maxEntries: Joi.number().empty("").label(this.labelMaxEntries),
|
||||||
refElementId: Joi.when("fieldType", {
|
refElementId: Joi.when("fieldType", {
|
||||||
is: Joi.string().valid("Sequence"),
|
is: Joi.string().valid("Sequence"),
|
||||||
then: Joi.object({
|
then: Joi.object({
|
||||||
id: Joi.optional(),
|
id: Joi.optional(),
|
||||||
guid: Joi.optional(),
|
guid: Joi.optional(),
|
||||||
|
}).required(),
|
||||||
|
}).when("fieldType", {
|
||||||
|
is: Joi.string().valid("Glossary"),
|
||||||
|
then: Joi.array()
|
||||||
|
.min(1)
|
||||||
|
.items(
|
||||||
|
Joi.object({
|
||||||
|
displayValue: Joi.string().optional(),
|
||||||
|
value: Joi.object({
|
||||||
|
id: Joi.optional(),
|
||||||
|
guid: Joi.optional(),
|
||||||
}).required(),
|
}).required(),
|
||||||
}).when("fieldType", {
|
}),
|
||||||
is: Joi.string().valid("Glossary"),
|
)
|
||||||
then: Joi.array()
|
.required(),
|
||||||
.min(1)
|
}),
|
||||||
.items(
|
minValue: Joi.number().allow("").label(this.labelMinValue),
|
||||||
Joi.object({
|
maxValue: Joi.number().allow("").label(this.labelMaxValue),
|
||||||
displayValue: Joi.string().optional(),
|
step: Joi.number().optional().allow("").min(0).label(this.labelStep),
|
||||||
value: Joi.object({
|
required: Joi.boolean().label(this.labelRequired),
|
||||||
id: Joi.optional(),
|
|
||||||
guid: Joi.optional(),
|
|
||||||
}).required(),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.required(),
|
|
||||||
}),
|
|
||||||
minValue: Joi.number().allow("").label(this.labelMinValue),
|
|
||||||
maxValue: Joi.number().allow("").label(this.labelMaxValue),
|
|
||||||
step: Joi.number().optional().allow("").min(0).label(this.labelStep),
|
|
||||||
required: Joi.boolean().label(this.labelRequired),
|
|
||||||
|
|
||||||
//defaultValue: Joi.string().allow("").label(this.labelDefaultValue)
|
//defaultValue: Joi.string().allow("").label(this.labelDefaultValue)
|
||||||
|
|
||||||
defaultValue: Joi.when("fieldType", {
|
defaultValue: Joi.when("fieldType", {
|
||||||
is: Joi.string().valid("Number"),
|
is: Joi.string().valid("Number"),
|
||||||
then: Joi.when("minValue", {
|
then: Joi.when("minValue", {
|
||||||
is: Joi.any().valid(null, ""),
|
is: Joi.any().valid(null, ""),
|
||||||
then: Joi.number(),
|
then: Joi.number(),
|
||||||
otherwise: Joi.number()
|
otherwise: Joi.number()
|
||||||
.min(Joi.ref("minValue"))
|
.min(Joi.ref("minValue"))
|
||||||
.message('"Default Value" must be greater than or equal to "' + this.labelMinValue + '"'),
|
.message(
|
||||||
})
|
'"Default Value" must be greater than or equal to "' +
|
||||||
.when("maxValue", {
|
this.labelMinValue +
|
||||||
is: Joi.any().valid(null, ""),
|
'"',
|
||||||
then: Joi.number(),
|
),
|
||||||
otherwise: Joi.number()
|
})
|
||||||
.max(Joi.ref("maxValue"))
|
.when("maxValue", {
|
||||||
.message('"Default Value" must be less than or equal to "' + this.labelMaxValue + '"'),
|
is: Joi.any().valid(null, ""),
|
||||||
})
|
then: Joi.number(),
|
||||||
.allow(""),
|
otherwise: Joi.number()
|
||||||
otherwise: Joi.string().allow(""),
|
.max(Joi.ref("maxValue"))
|
||||||
}).label(this.labelDefaultValue),
|
.message(
|
||||||
};
|
'"Default Value" must be less than or equal to "' +
|
||||||
|
this.labelMaxValue +
|
||||||
|
'"',
|
||||||
|
),
|
||||||
|
})
|
||||||
|
.allow(""),
|
||||||
|
otherwise: Joi.string().allow(""),
|
||||||
|
}).label(this.labelDefaultValue),
|
||||||
|
};
|
||||||
|
|
||||||
doSubmit = async (buttonName: string) => {
|
doSubmit = async (buttonName: string) => {
|
||||||
try {
|
try {
|
||||||
const { name, fieldType } = this.state.data;
|
const { name, fieldType } = this.state.data;
|
||||||
let { refElementId, defaultValue, minEntries, maxEntries, required } = this.state.data;
|
let { refElementId, defaultValue, minEntries, maxEntries, required } =
|
||||||
let numberParams: numberParams | undefined = undefined;
|
this.state.data;
|
||||||
let textParams: textParams | undefined = undefined;
|
let numberParams: numberParams | undefined = undefined;
|
||||||
let params;
|
let textParams: textParams | undefined = undefined;
|
||||||
let refElementIdValue: GeneralIdRef | undefined;
|
let params;
|
||||||
|
let refElementIdValue: GeneralIdRef | undefined;
|
||||||
|
|
||||||
switch (fieldType) {
|
switch (fieldType) {
|
||||||
case "Sequence":
|
case "Sequence":
|
||||||
minEntries = 1;
|
minEntries = 1;
|
||||||
maxEntries = 1;
|
maxEntries = 1;
|
||||||
defaultValue = "";
|
defaultValue = "";
|
||||||
refElementIdValue = refElementId as GeneralIdRef;
|
refElementIdValue = refElementId as GeneralIdRef;
|
||||||
break;
|
break;
|
||||||
case "FormTemplate":
|
case "FormTemplate":
|
||||||
minEntries = 1;
|
minEntries = 1;
|
||||||
maxEntries = 1;
|
maxEntries = 1;
|
||||||
defaultValue = "";
|
defaultValue = "";
|
||||||
break;
|
break;
|
||||||
case "Domain":
|
case "Domain":
|
||||||
minEntries = required ? 1 : 0;
|
minEntries = required ? 1 : 0;
|
||||||
maxEntries = maxEntries === 0 ? undefined : maxEntries;
|
maxEntries = maxEntries === 0 ? undefined : maxEntries;
|
||||||
defaultValue = "";
|
defaultValue = "";
|
||||||
break;
|
break;
|
||||||
case "Glossary":
|
case "Glossary":
|
||||||
minEntries = required ? 1 : 0;
|
minEntries = required ? 1 : 0;
|
||||||
maxEntries = maxEntries === 0 ? undefined : maxEntries;
|
maxEntries = maxEntries === 0 ? undefined : maxEntries;
|
||||||
defaultValue = "";
|
defaultValue = "";
|
||||||
refElementIdValue = (refElementId as CustomFieldValue[])[0].value as GeneralIdRef;
|
refElementIdValue = (refElementId as CustomFieldValue[])[0]
|
||||||
break;
|
.value as GeneralIdRef;
|
||||||
case "Text":
|
break;
|
||||||
minEntries = 1;
|
case "Text":
|
||||||
maxEntries = 1;
|
minEntries = 1;
|
||||||
let { multiLine } = this.state.data;
|
maxEntries = 1;
|
||||||
textParams = { multiLine };
|
let { multiLine } = this.state.data;
|
||||||
params = textParams;
|
textParams = { multiLine };
|
||||||
refElementIdValue = undefined;
|
params = textParams;
|
||||||
break;
|
refElementIdValue = undefined;
|
||||||
case "Number":
|
break;
|
||||||
refElementIdValue = undefined;
|
case "Number":
|
||||||
let { minValue, maxValue, step } = this.state.data;
|
refElementIdValue = undefined;
|
||||||
numberParams = { minValue, maxValue, step };
|
let { minValue, maxValue, step } = this.state.data;
|
||||||
params = numberParams;
|
numberParams = { minValue, maxValue, step };
|
||||||
minEntries = required ? 1 : 0;
|
params = numberParams;
|
||||||
maxEntries = 1;
|
minEntries = required ? 1 : 0;
|
||||||
break;
|
maxEntries = 1;
|
||||||
default:
|
break;
|
||||||
refElementIdValue = undefined;
|
default:
|
||||||
}
|
refElementIdValue = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
const cleanMaxEntries: Number | undefined = maxEntries === "" ? undefined : Number(maxEntries);
|
const cleanMaxEntries: Number | undefined =
|
||||||
|
maxEntries === "" ? undefined : Number(maxEntries);
|
||||||
|
|
||||||
if (this.isEditMode()) {
|
if (this.isEditMode()) {
|
||||||
const { customFieldId } = this.props.router.params;
|
|
||||||
|
|
||||||
var generalIdRef = MakeGeneralIdRef(customFieldId);
|
|
||||||
const response = await customFieldsService.putField(
|
|
||||||
generalIdRef,
|
|
||||||
name,
|
|
||||||
fieldType,
|
|
||||||
defaultValue,
|
|
||||||
minEntries,
|
|
||||||
cleanMaxEntries,
|
|
||||||
refElementIdValue,
|
|
||||||
params
|
|
||||||
);
|
|
||||||
if (response) {
|
|
||||||
toast.info("Custom Field edited");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const response = await customFieldsService.postField(name, fieldType, defaultValue, minEntries, cleanMaxEntries, refElementIdValue, params);
|
|
||||||
if (response) {
|
|
||||||
toast.info("New Custom Field added");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buttonName === this.labelSave) this.setState({ redirect: "/customfields" });
|
|
||||||
} catch (ex: any) {
|
|
||||||
this.handleGeneralError(ex);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
isEditMode = () => {
|
|
||||||
const { editMode } = this.props;
|
|
||||||
return editMode;
|
|
||||||
};
|
|
||||||
|
|
||||||
componentDidMount = async () => {
|
|
||||||
const { customFieldId } = this.props.router.params;
|
const { customFieldId } = this.props.router.params;
|
||||||
|
|
||||||
if (customFieldId !== undefined) {
|
var generalIdRef = MakeGeneralIdRef(customFieldId);
|
||||||
try {
|
const response = await customFieldsService.putField(
|
||||||
const loadedData = await customFieldsService.getField(customFieldId);
|
generalIdRef,
|
||||||
|
name,
|
||||||
const { data } = this.state;
|
fieldType,
|
||||||
if (loadedData) {
|
defaultValue,
|
||||||
data.name = loadedData.name;
|
minEntries,
|
||||||
data.fieldType = loadedData.fieldType;
|
cleanMaxEntries,
|
||||||
data.defaultValue = loadedData.defaultValue;
|
refElementIdValue,
|
||||||
data.minEntries = loadedData.minEntries;
|
params,
|
||||||
data.maxEntries = loadedData.maxEntries;
|
|
||||||
switch (data.fieldType) {
|
|
||||||
case "Glossary":
|
|
||||||
let convertedRefElementId: CustomFieldValue = {
|
|
||||||
value: loadedData.refElementId,
|
|
||||||
};
|
|
||||||
|
|
||||||
data.refElementId = [convertedRefElementId];
|
|
||||||
data.required = loadedData.minEntries > 0;
|
|
||||||
break;
|
|
||||||
case "Sequence":
|
|
||||||
data.refElementId = loadedData.refElementId;
|
|
||||||
break;
|
|
||||||
case "Domain":
|
|
||||||
data.required = loadedData.minEntries > 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (loadedData.parameters !== undefined) {
|
|
||||||
switch (data.fieldType) {
|
|
||||||
case "Number":
|
|
||||||
data.required = loadedData.minEntries > 0;
|
|
||||||
const parameters: numberParams = JSON.parse(loadedData.parameters);
|
|
||||||
data.minValue = parameters.minValue ?? undefined;
|
|
||||||
data.maxValue = parameters.maxValue ?? undefined;
|
|
||||||
data.step = parameters.step ?? undefined;
|
|
||||||
break;
|
|
||||||
case "Text":
|
|
||||||
const textParameters: textParams = JSON.parse(loadedData.parameters);
|
|
||||||
data.multiLine = textParameters.multiLine ?? false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ loaded: true, data });
|
|
||||||
} else {
|
|
||||||
this.setState({ loaded: false });
|
|
||||||
}
|
|
||||||
} catch (ex: any) {
|
|
||||||
this.handleGeneralError(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.isEditMode()) this.setState({ loaded: true });
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { loaded, redirect } = this.state;
|
|
||||||
if (redirect !== "") return <Navigate to={redirect} />;
|
|
||||||
|
|
||||||
const { fieldType, minValue, maxValue, step } = this.state.data;
|
|
||||||
|
|
||||||
let mode = "Add";
|
|
||||||
if (this.isEditMode()) mode = "Edit";
|
|
||||||
|
|
||||||
const fieldTypeOptions: Option[] = [
|
|
||||||
{ _id: "Text", name: "Text" },
|
|
||||||
{ _id: "Number", name: "Number" },
|
|
||||||
// { _id: "Boolean", name: "Boolean" },
|
|
||||||
// { _id: "Date", name: "Date" },
|
|
||||||
// { _id: "Time", name: "Time" },
|
|
||||||
// { _id: "DateTime", name: "DateTime" },
|
|
||||||
{ _id: "Sequence", name: "Sequence" },
|
|
||||||
{ _id: "FormTemplate", name: "Form Template" },
|
|
||||||
{ _id: "Glossary", name: "Glossary" },
|
|
||||||
{ _id: "Domain", name: "Domain" },
|
|
||||||
];
|
|
||||||
|
|
||||||
switch (fieldType) {
|
|
||||||
case "Sequence":
|
|
||||||
this.labelRefElementId = "Sequence";
|
|
||||||
break;
|
|
||||||
case "FormTemplate":
|
|
||||||
this.labelRefElementId = "Form";
|
|
||||||
break;
|
|
||||||
case "Glossary":
|
|
||||||
this.labelRefElementId = "Glossary";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Loading loaded={loaded}>
|
|
||||||
<h1>{mode} Custom Field</h1>
|
|
||||||
<form onSubmit={this.handleSubmit}>
|
|
||||||
{this.renderError("_general")}
|
|
||||||
{this.renderInput("name", this.labelName, InputType.text)}
|
|
||||||
{this.renderSelect("fieldType", this.labelFieldType, fieldTypeOptions)}
|
|
||||||
<Switch>
|
|
||||||
<Case condition={this.state.data.fieldType === "Domain"}>
|
|
||||||
{this.renderInput("required", this.labelRequired, InputType.checkbox)}
|
|
||||||
{this.renderInput("maxEntries", this.labelMaxEntries, InputType.number)}
|
|
||||||
</Case>
|
|
||||||
<Case condition={this.state.data.fieldType === "Glossary"}>
|
|
||||||
{this.renderGlossaryPicker(true, "refElementId", this.labelRefElementId, 1, SystemGlossaries)}
|
|
||||||
{this.renderInput("required", this.labelRequired, InputType.checkbox)}
|
|
||||||
{this.renderInput("maxEntries", this.labelMaxEntries, InputType.number)}
|
|
||||||
</Case>
|
|
||||||
<Case condition={this.state.data.fieldType === "Sequence"}>
|
|
||||||
{this.renderSequencePicker(true, "refElementId", this.labelRefElementId)}
|
|
||||||
</Case>
|
|
||||||
<Case condition={this.state.data.fieldType === "FormTemplate"}>
|
|
||||||
<></>
|
|
||||||
</Case>
|
|
||||||
<Case condition={this.state.data.fieldType === "Text"}>
|
|
||||||
{this.renderInput("multiLine", this.labelMultiLine, InputType.checkbox)}
|
|
||||||
<Case condition={this.state.data.multiLine === true}>{this.renderInputTextarea(true, "defaultValue", this.labelDefaultValue)}</Case>
|
|
||||||
<Case condition={this.state.data.multiLine === false}>
|
|
||||||
{this.renderInput("defaultValue", this.labelDefaultValue, InputType.text)}
|
|
||||||
</Case>
|
|
||||||
</Case>
|
|
||||||
<Case condition={this.state.data.fieldType === "Number"}>
|
|
||||||
{this.renderInput("required", this.labelRequired, InputType.checkbox)}
|
|
||||||
{this.renderInputNumber("minValue", this.labelMinValue, false, undefined, undefined, maxValue, undefined)}
|
|
||||||
{this.renderInputNumber("maxValue", this.labelMaxValue, false, undefined, minValue, undefined, undefined)}
|
|
||||||
{this.renderInput("step", this.labelStep, InputType.number)}
|
|
||||||
{this.renderInputNumber("defaultValue", this.labelDefaultValue, false, undefined, minValue, maxValue, step)}
|
|
||||||
</Case>
|
|
||||||
<Else>
|
|
||||||
{this.renderInput("defaultValue", this.labelDefaultValue, InputType.text)}
|
|
||||||
{this.renderInput("minEntries", this.labelMinEntries, InputType.number)}
|
|
||||||
{this.renderInput("maxEntries", this.labelMaxEntries, InputType.number)}
|
|
||||||
</Else>
|
|
||||||
</Switch>
|
|
||||||
{this.isEditMode() && this.renderButton(this.labelApply)}
|
|
||||||
{this.renderButton(this.labelSave)}
|
|
||||||
</form>
|
|
||||||
</Loading>
|
|
||||||
);
|
);
|
||||||
|
if (response) {
|
||||||
|
toast.info("Custom Field edited");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const response = await customFieldsService.postField(
|
||||||
|
name,
|
||||||
|
fieldType,
|
||||||
|
defaultValue,
|
||||||
|
minEntries,
|
||||||
|
cleanMaxEntries,
|
||||||
|
refElementIdValue,
|
||||||
|
params,
|
||||||
|
);
|
||||||
|
if (response) {
|
||||||
|
toast.info("New Custom Field added");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonName === this.labelSave)
|
||||||
|
this.setState({ redirect: "/customfields" });
|
||||||
|
} catch (ex: any) {
|
||||||
|
this.handleGeneralError(ex);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
isEditMode = () => {
|
||||||
|
const { editMode } = this.props;
|
||||||
|
return editMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount = async () => {
|
||||||
|
const { customFieldId } = this.props.router.params;
|
||||||
|
|
||||||
|
if (customFieldId !== undefined) {
|
||||||
|
try {
|
||||||
|
const loadedData = await customFieldsService.getField(customFieldId);
|
||||||
|
|
||||||
|
const { data } = this.state;
|
||||||
|
if (loadedData) {
|
||||||
|
data.name = loadedData.name;
|
||||||
|
data.fieldType = loadedData.fieldType;
|
||||||
|
data.defaultValue = loadedData.defaultValue;
|
||||||
|
data.minEntries = loadedData.minEntries;
|
||||||
|
data.maxEntries = loadedData.maxEntries;
|
||||||
|
switch (data.fieldType) {
|
||||||
|
case "Glossary":
|
||||||
|
let convertedRefElementId: CustomFieldValue = {
|
||||||
|
value: loadedData.refElementId,
|
||||||
|
};
|
||||||
|
|
||||||
|
data.refElementId = [convertedRefElementId];
|
||||||
|
data.required = loadedData.minEntries > 0;
|
||||||
|
break;
|
||||||
|
case "Sequence":
|
||||||
|
data.refElementId = loadedData.refElementId;
|
||||||
|
break;
|
||||||
|
case "Domain":
|
||||||
|
data.required = loadedData.minEntries > 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loadedData.parameters !== undefined) {
|
||||||
|
switch (data.fieldType) {
|
||||||
|
case "Number":
|
||||||
|
data.required = loadedData.minEntries > 0;
|
||||||
|
const parameters: numberParams = JSON.parse(
|
||||||
|
loadedData.parameters,
|
||||||
|
);
|
||||||
|
data.minValue = parameters.minValue ?? undefined;
|
||||||
|
data.maxValue = parameters.maxValue ?? undefined;
|
||||||
|
data.step = parameters.step ?? undefined;
|
||||||
|
break;
|
||||||
|
case "Text":
|
||||||
|
const textParameters: textParams = JSON.parse(
|
||||||
|
loadedData.parameters,
|
||||||
|
);
|
||||||
|
data.multiLine = textParameters.multiLine ?? false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ loaded: true, data });
|
||||||
|
} else {
|
||||||
|
this.setState({ loaded: false });
|
||||||
|
}
|
||||||
|
} catch (ex: any) {
|
||||||
|
this.handleGeneralError(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isEditMode()) this.setState({ loaded: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { loaded, redirect } = this.state;
|
||||||
|
if (redirect !== "") return <Navigate to={redirect} />;
|
||||||
|
|
||||||
|
const { fieldType, minValue, maxValue, step } = this.state.data;
|
||||||
|
|
||||||
|
let mode = "Add";
|
||||||
|
if (this.isEditMode()) mode = "Edit";
|
||||||
|
|
||||||
|
const fieldTypeOptions: Option[] = [
|
||||||
|
{ _id: "Text", name: "Text" },
|
||||||
|
{ _id: "Number", name: "Number" },
|
||||||
|
// { _id: "Boolean", name: "Boolean" },
|
||||||
|
// { _id: "Date", name: "Date" },
|
||||||
|
// { _id: "Time", name: "Time" },
|
||||||
|
// { _id: "DateTime", name: "DateTime" },
|
||||||
|
{ _id: "Sequence", name: "Sequence" },
|
||||||
|
{ _id: "FormTemplate", name: "Form Template" },
|
||||||
|
{ _id: "Glossary", name: "Glossary" },
|
||||||
|
{ _id: "Domain", name: "Domain" },
|
||||||
|
];
|
||||||
|
|
||||||
|
switch (fieldType) {
|
||||||
|
case "Sequence":
|
||||||
|
this.labelRefElementId = "Sequence";
|
||||||
|
break;
|
||||||
|
case "FormTemplate":
|
||||||
|
this.labelRefElementId = "Form";
|
||||||
|
break;
|
||||||
|
case "Glossary":
|
||||||
|
this.labelRefElementId = "Glossary";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Loading loaded={loaded}>
|
||||||
|
<h1>{mode} Custom Field</h1>
|
||||||
|
<form onSubmit={this.handleSubmit}>
|
||||||
|
{this.renderError("_general")}
|
||||||
|
{this.renderInput("name", this.labelName, InputType.text)}
|
||||||
|
{this.renderSelect(
|
||||||
|
"fieldType",
|
||||||
|
this.labelFieldType,
|
||||||
|
fieldTypeOptions,
|
||||||
|
)}
|
||||||
|
{this.state.data.fieldType === "Domain" && (
|
||||||
|
<>
|
||||||
|
{this.renderInput(
|
||||||
|
"required",
|
||||||
|
this.labelRequired,
|
||||||
|
InputType.checkbox,
|
||||||
|
)}
|
||||||
|
{this.renderInput(
|
||||||
|
"maxEntries",
|
||||||
|
this.labelMaxEntries,
|
||||||
|
InputType.number,
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{this.state.data.fieldType === "Glossary" && (
|
||||||
|
<>
|
||||||
|
{this.renderGlossaryPicker(
|
||||||
|
true,
|
||||||
|
"refElementId",
|
||||||
|
this.labelRefElementId,
|
||||||
|
1,
|
||||||
|
SystemGlossaries,
|
||||||
|
)}
|
||||||
|
{this.renderInput(
|
||||||
|
"required",
|
||||||
|
this.labelRequired,
|
||||||
|
InputType.checkbox,
|
||||||
|
)}
|
||||||
|
{this.renderInput(
|
||||||
|
"maxEntries",
|
||||||
|
this.labelMaxEntries,
|
||||||
|
InputType.number,
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{this.state.data.fieldType === "Sequence" && (
|
||||||
|
<>
|
||||||
|
{this.renderSequencePicker(
|
||||||
|
true,
|
||||||
|
"refElementId",
|
||||||
|
this.labelRefElementId,
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{this.state.data.fieldType === "Text" && (
|
||||||
|
<>
|
||||||
|
{this.renderInput(
|
||||||
|
"multiLine",
|
||||||
|
this.labelMultiLine,
|
||||||
|
InputType.checkbox,
|
||||||
|
)}
|
||||||
|
{this.state.data.multiLine === true &&
|
||||||
|
this.renderInputTextarea(
|
||||||
|
true,
|
||||||
|
"defaultValue",
|
||||||
|
this.labelDefaultValue,
|
||||||
|
)}
|
||||||
|
{this.state.data.multiLine === false &&
|
||||||
|
this.renderInput(
|
||||||
|
"defaultValue",
|
||||||
|
this.labelDefaultValue,
|
||||||
|
InputType.text,
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{this.state.data.fieldType === "Number" && (
|
||||||
|
<>
|
||||||
|
{this.renderInput(
|
||||||
|
"required",
|
||||||
|
this.labelRequired,
|
||||||
|
InputType.checkbox,
|
||||||
|
)}
|
||||||
|
{this.renderInputNumber(
|
||||||
|
"minValue",
|
||||||
|
this.labelMinValue,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
maxValue,
|
||||||
|
undefined,
|
||||||
|
)}
|
||||||
|
{this.renderInputNumber(
|
||||||
|
"maxValue",
|
||||||
|
this.labelMaxValue,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
minValue,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
)}
|
||||||
|
{this.renderInput("step", this.labelStep, InputType.number)}
|
||||||
|
{this.renderInputNumber(
|
||||||
|
"defaultValue",
|
||||||
|
this.labelDefaultValue,
|
||||||
|
false,
|
||||||
|
undefined,
|
||||||
|
minValue,
|
||||||
|
maxValue,
|
||||||
|
step,
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{![
|
||||||
|
"Domain",
|
||||||
|
"Glossary",
|
||||||
|
"Sequence",
|
||||||
|
"FormTemplate",
|
||||||
|
"Text",
|
||||||
|
"Number",
|
||||||
|
].includes(this.state.data.fieldType) && (
|
||||||
|
<>
|
||||||
|
{this.renderInput(
|
||||||
|
"defaultValue",
|
||||||
|
this.labelDefaultValue,
|
||||||
|
InputType.text,
|
||||||
|
)}
|
||||||
|
{this.renderInput(
|
||||||
|
"minEntries",
|
||||||
|
this.labelMinEntries,
|
||||||
|
InputType.number,
|
||||||
|
)}
|
||||||
|
{this.renderInput(
|
||||||
|
"maxEntries",
|
||||||
|
this.labelMaxEntries,
|
||||||
|
InputType.number,
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{this.isEditMode() && this.renderButton(this.labelApply)}
|
||||||
|
{this.renderButton(this.labelSave)}
|
||||||
|
</form>
|
||||||
|
</Loading>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const HOCCustomFieldDetails = withRouter(CustomFieldDetails);
|
const HOCCustomFieldDetails = withRouter(CustomFieldDetails);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user