diff --git a/public/locales/en/common.json b/public/locales/en/common.json index b770e0d..168acae 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1,7 +1,10 @@ { "Activate": "Activate", "Admin": "Admin", + "AnEmailWithPasswordResetLinkHasBeenSent": "An email with a password reset link has been sent.", "AnErrorOccurred": "An error occurred", + "Application": "Application", + "Applications": "Applications", "AuditLog": "Audit Logs", "AuditLogs": "Audit Logs", "BlockedIPAddresses": "Blocked IP addresses", @@ -13,42 +16,49 @@ "ConfirmPassword": "Confirm Password", "CustomFieldManager": "Custom Field Manager", "CustomFields": "Custom Fields", + "DisableAuthenticator": "Disable Authenticator", "DisplayName": "Display Name", - "EntityDisplayName": "Entity Display Name", "e-print": "e-print", "e-suite": "e-suite", + "e-suiteLogo": "e-suite logo", + "EntityDisplayName": "Entity Display Name", "ErrorLogs": "Error Logs", "ExceptionJson": "Exception JSON", "ExceptionLogs": "Exception Logs", + "FailedToDisableAuthenticator": "Failed to disable authenticator:", "Forms": "Forms", "FormTemplateManager": "Form Template Manager", "Glossaries": "Glossaries", "GlossaryManager": "Glossary Manager", "Home": "Home", "Id": "Id", - "Application": "Application", - "Message": "Message", - "ShowJSON": "Show JSON", - "ShowStackTrace": "Show stack trace", - "OccuredAt": "Occured At", "IPAddress": "IP Address", "IPAddressUnblocked": "IP Address '{{ip}}' unblocked.", "Loading": "Loading", + "LoggingOut": "Logging out", + "Message": "Message", "Name": "Name", - "NumberOfAttempts": "Number Of Attempts", + "NewPassword": "New Password", "NewValue": "New Value", + "NotFound": "Not found", + "NumberOfAttempts": "Number Of Attempts", + "OccuredAt": "Occured At", "OldValue": "Old Value", "Password": "Password", "PasswordIsRequired": "Password is required", "PasswordMinLength": "Password must be at least {{minPasswordLength}} characters", "PasswordsMustMatch": "You need to confirm by typing exactly the same as the new password", "PressAgainToUnblock": "Press again to unblock", + "ResetPassword": "Reset Password", + "Save": "Save", "Sequence": "Sequence", "SequenceManager": "Sequence Manager", + "ShowJSON": "Show JSON", + "ShowStackTrace": "Show stack trace", "SiteManager": "Site Manager", "SpecificationManager": "Specification Manager", - "StackTrace": "Stack Trace", "SsoManager": "Sso Manager", + "StackTrace": "Stack Trace", "Support": "Support", "SupportingData": "Supporting Data", "Timing": "Timing", @@ -56,5 +66,7 @@ "UnblockedInMinutes": "Unblocked In (Minutes)", "UserManager": "User Manager", "UserName": "User Name", + "UsernameIsRequired": "Username is required", + "UsernameMustBeValidEmail": "Username must be a valid email", "Users": "Users" } diff --git a/src/img/logo.tsx b/src/img/logo.tsx index 6919f27..5c9e095 100644 --- a/src/img/logo.tsx +++ b/src/img/logo.tsx @@ -1,15 +1,26 @@ 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 { - className? : string; - height? : string - width? : string - alt?: string + className?: string; + height?: string; + width?: string; } - -const Logo: FunctionComponent = (props:LogoProps) => { - return ( {props.alt}); -} - -export default Logo; \ No newline at end of file + +const Logo: FunctionComponent = (props: LogoProps) => { + const { t } = useTranslation(Namespaces.Common); + + return ( + {t("e-suiteLogo") + ); +}; + +export default Logo; diff --git a/src/modules/errorLogs/components/errorLogsTable.tsx b/src/modules/errorLogs/components/errorLogsTable.tsx index 43c8e23..c105229 100644 --- a/src/modules/errorLogs/components/errorLogsTable.tsx +++ b/src/modules/errorLogs/components/errorLogsTable.tsx @@ -6,7 +6,6 @@ import { useTranslation } from "react-i18next"; import { Namespaces } from "../../../i18n/i18n"; import ExpandableCell from "../../../components/common/ExpandableCell"; -import { max } from "date-fns"; export default function ErrorLogsTable( props: PublishedTableProps, diff --git a/src/modules/frame/components/EmailUserActionDisableTwoFactorAuthentication.tsx b/src/modules/frame/components/EmailUserActionDisableTwoFactorAuthentication.tsx index d77ae83..9e450a9 100644 --- a/src/modules/frame/components/EmailUserActionDisableTwoFactorAuthentication.tsx +++ b/src/modules/frame/components/EmailUserActionDisableTwoFactorAuthentication.tsx @@ -1,63 +1,63 @@ -import Form, { FormState, FormData } from "../../../components/common/Form"; -import Joi from "joi"; +import React, { useState } from "react"; import authentication from "../services/authenticationService"; import { IEmailUserAction } from "../models/IEmailUserAction"; +import { FormData } from "../../../components/common/Form"; import Button, { ButtonType } from "../../../components/common/Button"; +import { useTranslation } from "react-i18next"; +import { Namespaces } from "../../../i18n/i18n"; export interface EmailUserActionDiableTwoFactorAuthenticationData extends FormData { - authenticatorDisabled: boolean; + authenticatorDisabled: boolean; } -export interface EmailUserActionDiableTwoFactorAuthenticationState extends FormState { - data: EmailUserActionDiableTwoFactorAuthenticationData; +interface Props { + emailUserAction: IEmailUserAction; } -class EmailUserActionDiableTwoFactorAuthentication extends Form { - state = { - loaded: true, - data: { authenticatorDisabled: false }, - errors: {}, +const EmailUserActionDiableTwoFactorAuthentication: React.FC = ({ + emailUserAction, +}) => { + const { t } = useTranslation(); + 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"; - - schema = { - authenticatorDisabled: Joi.boolean(), - }; - - doSubmit = async () => { - 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
Your authenticator has been disabled. You can now log in without two factor authentication
; - } - - return ( - <> -
Disable two factor authentication
- - - - ); + try { + const callResult = await authentication.completeEmailAction(action); + if (callResult === 1) { + setAuthenticatorDisabled(true); + } + } catch (error) { + console.error(t("FailedToDisableAuthenticator"), error); } -} + }; + + if (authenticatorDisabled) { + return ( +
+ Your authenticator has been disabled. You can now log in without two + factor authentication +
+ ); + } + + return ( + <> +
Disable two factor authentication
+ + + + ); +}; export default EmailUserActionDiableTwoFactorAuthentication; diff --git a/src/modules/frame/components/EmailUserActionPasswordReset.tsx b/src/modules/frame/components/EmailUserActionPasswordReset.tsx index 1bbd0a0..d49c155 100644 --- a/src/modules/frame/components/EmailUserActionPasswordReset.tsx +++ b/src/modules/frame/components/EmailUserActionPasswordReset.tsx @@ -1,136 +1,173 @@ -import Joi from "joi"; -import Form, { businessValidationResult, FormData, FormState } from "../../../components/common/Form"; +import React, { useState } from "react"; import { InputType } from "../../../components/common/Input"; import { IEmailUserAction } from "../models/IEmailUserAction"; 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 { - password: string; - confirmPassword: string; - passwordChanged: boolean; + password: string; + confirmPassword: string; + passwordChanged: boolean; } -export interface EmailUserActionPasswordResetState extends FormState { - data: EmailUserActionPasswordResetData; +interface Props { + emailUserAction: IEmailUserAction; } -class EmailUserActionPasswordReset extends Form { - state = { - loaded: true, - passwordMaxLenght: 255, - data: { password: "", confirmPassword: "", passwordChanged: false }, - errors: {}, - hasTwelveCharacters: false, - hasSpecialCharacter: false, - hasUppercaseLetter: false, - hasLowercaseLetter: false, - hasNumber: false - }; +const EmailUserActionPasswordReset: React.FC = ({ emailUserAction }) => { + const { t } = useTranslation(); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [passwordChanged, setPasswordChanged] = useState(false); + const [generalError, setGeneralError] = useState(""); + const [errors, setErrors] = useState<{ [key: string]: string }>({}); + const [hasTwelveCharacters, setHasTwelveCharacters] = useState(false); + const [hasSpecialCharacter, setHasSpecialCharacter] = useState(false); + const [hasUppercaseLetter, setHasUppercaseLetter] = useState(false); + const [hasLowercaseLetter, setHasLowercaseLetter] = useState(false); + const [hasNumber, setHasNumber] = useState(false); - labelPassword = "New Password"; - labelConfirmPassword = "Confirm Password"; - labelChangePassword = "Save"; + const LABEL_PASSWORD = t("NewPassword"); + const LABEL_CONFIRM_PASSWORD = t("ConfirmPassword"); + const LABEL_CHANGE_PASSWORD = t("Save"); + const PASSWORD_MAX_LENGTH = 255; - schema = { - password: Joi.string().required().min(12).label(this.labelPassword), - confirmPassword: Joi.string() - .when("password", { - is: "", - then: Joi.optional(), - otherwise: Joi.valid(Joi.ref("password")).error(() => { - const e = new Error("Passwords must match"); - e.name = "confirmPassword"; - return e; - }), - }) - .label(this.labelConfirmPassword), + const handlePasswordChange = (e: React.ChangeEvent) => { + const newPassword = e.currentTarget.value; + setPassword(newPassword); + setConfirmPassword(""); + setHasNumber(/\d+/g.test(newPassword)); + setHasLowercaseLetter(/[a-z]/g.test(newPassword)); + setHasUppercaseLetter(/[A-Z]/g.test(newPassword)); + setHasSpecialCharacter( + /[ ~`! @#$%^&*()_+\-=[\]{};:\\|,.'"<>/?]/.test(newPassword), + ); + setHasTwelveCharacters(newPassword.length >= 12); + }; - passwordChanged: Joi.boolean(), - }; + const handleConfirmPasswordChange = ( + e: React.ChangeEvent, + ) => { + setConfirmPassword(e.currentTarget.value); + }; - BusinessValidation(): businessValidationResult | null { - const { password, confirmPassword } = this.state.data; + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const newErrors: { [key: string]: string } = {}; - if (password !== confirmPassword) { - return { - details: [ - { - path: "confirmPassword", - message: "You need to confirm by typing exactly the same as the new password", - }, - ], - }; - } + const minPasswordLength = 12; - 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) => { - const { data } = this.state; - 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
Your password has been reset. Please contact your admin if this wasn't you.
; - } - - return ( - <> -
- {this.renderError("_general")} - {this.renderInputWithChangeEvent("password", "", InputType.password, undefined, this.handlePasswordChange, undefined, this.labelPassword, passwordMaxLenght)} -
Password requires a minimum of 12 characters containing a combination of:
-
    -
  • At least 1 symbol
  • -
  • At least 1 number
  • -
  • At least 1 lowercase letter
  • -
  • At least 1 uppercase letter
  • -
- {this.renderInput("confirmPassword", "", InputType.password, undefined, undefined, this.labelConfirmPassword, passwordMaxLenght)} - {this.renderButton(this.labelChangePassword, "save", undefined, undefined, isFormValid)} -
- - ); + if (password && password !== confirmPassword) { + newErrors.confirmPassword = t("PasswordsMustMatch"); } -} + + 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 ( +
+ Your password has been reset. Please contact your admin if this wasn't + you. +
+ ); + } + + return ( + <> +
+ {generalError && } + +
+ Password requires a minimum of 12 characters containing a combination + of: +
+
    +
  • + At least 1 symbol +
  • +
  • At least 1 number
  • +
  • + At least 1 lowercase letter +
  • +
  • + At least 1 uppercase letter +
  • +
+ + + + + ); +}; export default EmailUserActionPasswordReset; diff --git a/src/modules/frame/components/ForgotPassword.tsx b/src/modules/frame/components/ForgotPassword.tsx index 924dfb9..9706585 100644 --- a/src/modules/frame/components/ForgotPassword.tsx +++ b/src/modules/frame/components/ForgotPassword.tsx @@ -1,69 +1,86 @@ -import Joi from "joi"; -import Form, { FormData, FormState } from "../../../components/common/Form"; +import React, { useState } from "react"; +import { FormData } from "../../../components/common/Form"; 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 { - username: string; - emailSent: boolean; + username: string; + emailSent: boolean; } -export interface ForgotPasswordtate extends FormState { - data: ForgotPasswordData; -} +const ForgotPassword: React.FC = () => { + const { t } = useTranslation(); + const [username, setUsername] = useState(""); + const [emailSent, setEmailSent] = useState(false); + const [generalError, setGeneralError] = useState(""); + const [errors, setErrors] = useState<{ [key: string]: string }>({}); -class ForgotPassword extends Form { - state = { - loaded: true, - data: { username: "", emailSent: false }, - errors: {}, - }; + const validateEmail = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); + }; - schema = { - username: Joi.string() - .required() - .email({ tlds: { allow: false } }) - .label("Username"), - emailSent: Joi.boolean().required(), - }; + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const newErrors: { [key: string]: string } = {}; - doSubmit = async (buttonName : string) => { - try { - let { data } = this.state; - - const response = await authentication.forgotPassword(data.username); - if (response) { - data.emailSent = true; - this.setState({ data }); - } - } - catch(ex: any) { - this.handleGeneralError(ex); - } - }; - - render() { - const { emailSent } = this.state.data; - - let content = ( -
- {this.renderError("_general")} - {this.renderInput("username", "Username")} - {this.renderButton("Reset password")} -
- ); - if (emailSent) { - content =
An email with a password reset link has been sent.
; - } - - return ( -
-
-

Forgot password

-
- {content} -
- ); + // Validation + if (!username) { + newErrors.username = t("UsernameIsRequired"); + } else if (!validateEmail(username)) { + newErrors.username = t("UsernameMustBeValidEmail"); } -} + + 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 = ( +
+ {generalError && } + setUsername(e.currentTarget.value)} + error={errors.username} + /> + + + ); + + if (emailSent) { + content =
{t("AnEmailWithPasswordResetLinkHasBeenSent")}
; + } + + return ( +
+
+

Forgot password

+
+ {content} +
+ ); +}; export default ForgotPassword; diff --git a/src/modules/frame/components/LeftMenuItem.tsx b/src/modules/frame/components/LeftMenuItem.tsx index ac0d6f0..94713b9 100644 --- a/src/modules/frame/components/LeftMenuItem.tsx +++ b/src/modules/frame/components/LeftMenuItem.tsx @@ -1,49 +1,45 @@ import { IconDefinition } from "@fortawesome/pro-thin-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import React, { Component } from "react"; -import { Link } from "react-router-dom"; -import withRouter, { RouterProps } from "../../../utils/withRouter"; +import React from "react"; +import { Link, useLocation } from "react-router-dom"; -interface LeftMenuItemProps extends RouterProps { - to : string; - icon? : IconDefinition; - label : string; +interface LeftMenuItemProps { + to: string; + icon?: IconDefinition; + label: string; } -class LOCLeftMenuItem extends Component { - isSelected = ():boolean => { - const { to } = this.props; - const { pathname } = this.props.router.location; - let isSelected : boolean = false; - if (to === '/' ? (pathname === to) : pathname.toLowerCase().startsWith(to)){ - isSelected = true; - } +const LeftMenuItem: React.FC = ({ to, icon, label }) => { + const location = useLocation(); - return isSelected; - } + const isSelected = (): boolean => { + const pathname = location.pathname; + return to === "/" ? pathname === to : pathname.toLowerCase().startsWith(to); + }; - render() { - const { to, icon, label } = this.props; + let className = ""; - let className = ""; + if (isSelected()) { + className += " leftMenuSelected"; + } - if (this.isSelected()) { - className += " leftMenuSelected"; - } + if (icon) { + return ( +
+ + +
{label}
+ +
+ ); + } - if ( icon) { - return ( -
{label}
- ); - } + return ( + + {label} + + ); +}; - return ( - {label} - ); - } -} - -const LeftMenuItem = withRouter(LOCLeftMenuItem); - export default LeftMenuItem; -export { LOCLeftMenuItem }; +export { LeftMenuItem as LOCLeftMenuItem }; diff --git a/src/modules/frame/components/LeftMenuSubMenu.tsx b/src/modules/frame/components/LeftMenuSubMenu.tsx index 5f00544..575550c 100644 --- a/src/modules/frame/components/LeftMenuSubMenu.tsx +++ b/src/modules/frame/components/LeftMenuSubMenu.tsx @@ -3,71 +3,74 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React from "react"; import withRouter, { RouterProps } from "../../../utils/withRouter"; -interface LeftMenuSubMenuProps extends RouterProps{ - icon : IconDefinition; - label : string; - openMenu? : LOCLeftMenuSubMenu; - children : (false | JSX.Element)[]; - onClick? : ( menuItem : LOCLeftMenuSubMenu ) => void; +interface LeftMenuSubMenuProps extends RouterProps { + icon: IconDefinition; + label: string; + openMenu?: LOCLeftMenuSubMenu; + children: (false | JSX.Element)[]; + 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 ( +
+ +
{label}
+
+ ); + } } - -class LOCLeftMenuSubMenu extends React.Component { - 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 (
{label}
); - } -} - const LeftMenuSubMenu = withRouter(LOCLeftMenuSubMenu); export default LeftMenuSubMenu; -export {LOCLeftMenuSubMenu} \ No newline at end of file +export { LOCLeftMenuSubMenu }; diff --git a/src/modules/frame/components/LoginForm.tsx b/src/modules/frame/components/LoginForm.tsx index 5e28cbf..aaa7c09 100644 --- a/src/modules/frame/components/LoginForm.tsx +++ b/src/modules/frame/components/LoginForm.tsx @@ -8,159 +8,223 @@ import { ButtonType } from "../../../components/common/Button"; //import '../../../Sass/login.scss'; export interface LoginFormStateData extends FormData { - username: string; - password: string; - tfaNeeded: boolean; - requestTfaRemoval: boolean; - securityCode: string; + username: string; + password: string; + tfaNeeded: boolean; + requestTfaRemoval: boolean; + securityCode: string; } export interface LoginFormState extends FormState { - passwordMaxLength: number, - isInNextStage: boolean, - emailSent: boolean, - data: LoginFormStateData; + passwordMaxLength: number; + isInNextStage: boolean; + emailSent: boolean; + data: LoginFormStateData; } class LoginForm extends Form { - state = { - loaded: true, - passwordMaxLength: 255, - isInNextStage: false, - emailSent: false, - data: { - username: "", - password: "", - tfaNeeded: false, - requestTfaRemoval: false, - securityCode: "", - }, - errors: {}, - }; + state = { + loaded: true, + passwordMaxLength: 255, + isInNextStage: false, + emailSent: false, + data: { + username: "", + password: "", + tfaNeeded: false, + requestTfaRemoval: false, + securityCode: "", + }, + errors: {}, + }; - schema = { - username: Joi.string() - .required() - .email({ tlds: { allow: false } }) - .label("Email"), - password: Joi.string().required().label("Password"), - tfaNeeded: Joi.boolean().required(), - requestTfaRemoval: Joi.boolean().required(), - securityCode: Joi.string().allow("").label("Authenticate"), - }; + schema = { + username: Joi.string() + .required() + .email({ tlds: { allow: false } }) + .label("Email"), + password: Joi.string().required().label("Password"), + tfaNeeded: Joi.boolean().required(), + requestTfaRemoval: Joi.boolean().required(), + securityCode: Joi.string().allow("").label("Authenticate"), + }; - doSubmit = async (buttonName : string) => { - const { data } = this.state; - await this.performLogin(data); - }; + doSubmit = async (buttonName: string) => { + const { data } = this.state; + await this.performLogin(data); + }; - handleNextClick = async (event: React.MouseEvent) => { - 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); - } + handleNextClick = async (event: React.MouseEvent) => { + 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); - } + 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); } + }; - 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; + authenticationWorkAround = async () => { + const data: LoginFormStateData = { ...this.state.data }; + data.requestTfaRemoval = true; - if (authentication.getCurrentUser()) return ; + await this.performLogin(data); - const requestTfaRemovalPanel =
An email has been sent to you so that you can regain control of your account.
; + this.setState({ data }); + }; - const loginPanel = ( - <>
- {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 &&
- {this.renderButton("Login", "login", undefined, "login", !emailSent)} -
- } -
- {isInNextStage &&
- {this.renderButton("Forgotten Password", "forgot-password", this.handleForgetPassword, "forgot-password", validEmail, ButtonType.secondary, true)} -
} - {emailSent &&
If you have a registered account, you will receive an email.
} - {this.renderError("_general")} - - ); + private async performLogin(data: LoginFormStateData) { + try { + let result = await authentication.login( + data.username, + data.password, + data.securityCode, + data.requestTfaRemoval, + ); - const tfaPanel = ( -
- {this.renderError("_general")} - {this.renderInput("securityCode", "Authenticate")} - {this.renderButton("Authenticate")} - - My Authenticator is not working - -
- ); + switch (result) { + case 1: //requires tfa + const { data } = this.state; - return ( -
- {requestTfaRemoval ? requestTfaRemovalPanel : tfaNeeded ? tfaPanel : loginPanel} + 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"); + + 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 ; + + const requestTfaRemovalPanel = ( +
+ An email has been sent to you so that you can regain control of your + account. +
+ ); + + const loginPanel = ( + <> +
+ {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 && ( +
+ {this.renderButton( + "Login", + "login", + undefined, + "login", + !emailSent, + )}
- ); - } + )} +
+ {isInNextStage && ( +
+ {this.renderButton( + "Forgotten Password", + "forgot-password", + this.handleForgetPassword, + "forgot-password", + validEmail, + ButtonType.secondary, + true, + )} +
+ )} + {emailSent && ( +
+ If you have a registered account, you will receive an email. +
+ )} + {this.renderError("_general")} + + ); + + const tfaPanel = ( +
+ {this.renderError("_general")} + {this.renderInput("securityCode", "Authenticate")} + {this.renderButton("Authenticate")} + + My Authenticator is not working + +
+ ); + + return ( +
+ {requestTfaRemoval + ? requestTfaRemovalPanel + : tfaNeeded + ? tfaPanel + : loginPanel} +
+ ); + } } export default LoginForm; diff --git a/src/modules/frame/components/Logout.tsx b/src/modules/frame/components/Logout.tsx index bd11cab..167e0c4 100644 --- a/src/modules/frame/components/Logout.tsx +++ b/src/modules/frame/components/Logout.tsx @@ -1,20 +1,20 @@ -import * as React from "react"; +import React, { useEffect } from "react"; +import { useTranslation } from "react-i18next"; import authentication from "../services/authenticationService"; -class Logout extends React.Component { - componentDidMount() { - authentication.logout(); - if (window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN) { - window.location.href = "/account/logout" - } - else { - window.location.href = "/" - } - } +const Logout: React.FC = () => { + const { t } = useTranslation(); - render() { - return
Logging out
; + useEffect(() => { + authentication.logout(); + if (window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN) { + window.location.href = "/account/logout"; + } else { + window.location.href = "/"; } -} + }, []); + + return
{t("LoggingOut")}
; +}; export default Logout; diff --git a/src/modules/frame/components/Mainframe.tsx b/src/modules/frame/components/Mainframe.tsx index a8a0091..1aa5fdb 100644 --- a/src/modules/frame/components/Mainframe.tsx +++ b/src/modules/frame/components/Mainframe.tsx @@ -1,23 +1,23 @@ -import * as React from "react"; +import React from "react"; import TopMenu from "./TopMenu"; import LeftMenu from "./LeftMenu"; import "../../../Sass/_frame.scss"; type MainFrameProps = { - title?: string | undefined | null; - children?: React.ReactNode; // 👈️ type children + title?: string | null; + children?: React.ReactNode; }; -const Mainframe = (props: MainFrameProps): JSX.Element => { +const Mainframe: React.FC = ({ title, children }) => { return (
- +
-
{props.children}
+
{children}
); diff --git a/src/modules/frame/components/NotFound.tsx b/src/modules/frame/components/NotFound.tsx index aef445d..12ac48c 100644 --- a/src/modules/frame/components/NotFound.tsx +++ b/src/modules/frame/components/NotFound.tsx @@ -1,7 +1,10 @@ -import * as React from "react"; +import React from "react"; +import { useTranslation } from "react-i18next"; -function NotFound() { - return

Not found

; -} +const NotFound: React.FC = () => { + const { t } = useTranslation(); + + return

{t("NotFound")}

; +}; export default NotFound; diff --git a/src/modules/frame/components/Switch.tsx b/src/modules/frame/components/Switch.tsx deleted file mode 100644 index f1142bd..0000000 --- a/src/modules/frame/components/Switch.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import * as React from "react"; - -export interface SwitchProps { - children: React.ReactNode; -} - -export class Switch extends React.PureComponent { - 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 ( - - { caseComponent } - - ); - } -} - -export interface CaseProps { - condition: boolean; - children: React.ReactNode; -} - -export class Case extends React.PureComponent { - render() { - const { condition, children } = this.props; - - return ( - - { condition ? children : null } - - ); - } -} - -export class Else extends React.PureComponent -{ - render() { - return this.props.children; - } -} \ No newline at end of file diff --git a/src/modules/frame/components/TopMenu.tsx b/src/modules/frame/components/TopMenu.tsx index b46a4db..0dfbc4c 100644 --- a/src/modules/frame/components/TopMenu.tsx +++ b/src/modules/frame/components/TopMenu.tsx @@ -12,7 +12,7 @@ import { getCurrentUser } from "../services/authenticationService"; import { LanguageSelectorMenuItem } from "./LanguageSelector"; export interface TopMenuProps { - title?: string; + title: string | undefined | null; } function TopMenu(props: TopMenuProps) { @@ -21,7 +21,7 @@ function TopMenu(props: TopMenuProps) { return ( - +
{props.title}
diff --git a/src/modules/frame/components/loginFrame.tsx b/src/modules/frame/components/loginFrame.tsx index 8e2f426..b021252 100644 --- a/src/modules/frame/components/loginFrame.tsx +++ b/src/modules/frame/components/loginFrame.tsx @@ -5,34 +5,24 @@ import "../../../Sass/login.scss"; import Logo from "../../../img/logo"; interface LoginFrameProps { - children?: JSX.Element + children?: JSX.Element; } -interface LoginFrameState { +const LoginFrame: React.FC = ({ children }) => { + return ( +
+
+
+
+ +
+
{children}
+
+
-} +
+
+ ); +}; -class LoginFrame extends React.Component { - - render() { - const { children } = this.props; - - return (
-
-
-
- -
-
- {children} -
-
-
- -
- -
); - } -} - -export default LoginFrame; \ No newline at end of file +export default LoginFrame; diff --git a/src/modules/homepage/Env.tsx b/src/modules/homepage/Env.tsx index da44ce3..ee37cae 100644 --- a/src/modules/homepage/Env.tsx +++ b/src/modules/homepage/Env.tsx @@ -1,13 +1,23 @@ -import * as React from "react"; +import React from "react"; -function EnvPage() { - return ( - <> -

This is the Environment

-

-

window.__RUNTIME_CONFIG__.API_URL = {window.__RUNTIME_CONFIG__.API_URL}

- - ); -} +const EnvPage: React.FC = () => { + return ( + <> +

This is the Environment

+
+

+ window.__RUNTIME_CONFIG__.NODE_ENV ={" "} + {window.__RUNTIME_CONFIG__.NODE_ENV} +

+

+ window.__RUNTIME_CONFIG__.API_URL = {window.__RUNTIME_CONFIG__.API_URL} +

+

+ window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ={" "} + {window.__RUNTIME_CONFIG__.EXTERNAL_LOGIN ? "true" : "false"} +

+ + ); +}; export default EnvPage; diff --git a/src/modules/homepage/HomePage.tsx b/src/modules/homepage/HomePage.tsx index 3e933c7..2379cd8 100644 --- a/src/modules/homepage/HomePage.tsx +++ b/src/modules/homepage/HomePage.tsx @@ -1,20 +1,24 @@ -import * as React from "react"; +import React from "react"; +import { useTranslation } from "react-i18next"; -function HomePage() { - const redirect = ()=> { - window.location.href = '/organisations' - } +const HomePage: React.FC = () => { + const { t } = useTranslation(); - return ( -
-

Applications

-
-
-
E-print
-
-
+ const redirect = () => { + window.location.href = "/organisations"; + }; + + return ( +
+

{t("Applications")}

+
+
+
+
{t("e-print")}
- ); -} +
+
+ ); +}; export default HomePage; diff --git a/src/modules/manager/customfields/customFieldDetails.tsx b/src/modules/manager/customfields/customFieldDetails.tsx index c859c5d..6f09a4e 100644 --- a/src/modules/manager/customfields/customFieldDetails.tsx +++ b/src/modules/manager/customfields/customFieldDetails.tsx @@ -7,351 +7,476 @@ import { InputType } from "../../../components/common/Input"; import { FormState } from "../../../components/common/Form"; import withRouter from "../../../utils/withRouter"; 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 { GeneralIdRef } from "./../../../utils/GeneralIdRef"; -import { Case, Else, Switch } from "../../frame/components/Switch"; -import { CustomFieldValue, SystemGlossaries } from "../glossary/services/glossaryService"; +import { + CustomFieldValue, + SystemGlossaries, +} from "../glossary/services/glossaryService"; import Loading from "../../../components/common/Loading"; interface CustomFieldDetailsState extends FormState { - data: { - name: string; - fieldType: string; - multiLine: boolean; - defaultValue: string; - minEntries: number; - maxEntries: string | number | undefined; - refElementId: CustomFieldValue[] | GeneralIdRef | undefined; - minValue: number | undefined; - maxValue: number | undefined; - step: number | undefined; - required: boolean; - }; - redirect: string; + data: { + name: string; + fieldType: string; + multiLine: boolean; + defaultValue: string; + minEntries: number; + maxEntries: string | number | undefined; + refElementId: CustomFieldValue[] | GeneralIdRef | undefined; + minValue: number | undefined; + maxValue: number | undefined; + step: number | undefined; + required: boolean; + }; + redirect: string; } class CustomFieldDetails extends Form { - state: CustomFieldDetailsState = { - loaded: false, - data: { - name: "", - fieldType: "Text", - defaultValue: "", - multiLine: false, - minEntries: 0, - maxEntries: 1, - refElementId: undefined, - minValue: undefined, - maxValue: undefined, - step: undefined, - required: false, - }, - errors: {}, - redirect: "", - }; + state: CustomFieldDetailsState = { + loaded: false, + data: { + name: "", + fieldType: "Text", + defaultValue: "", + multiLine: false, + minEntries: 0, + maxEntries: 1, + refElementId: undefined, + minValue: undefined, + maxValue: undefined, + step: undefined, + required: false, + }, + errors: {}, + redirect: "", + }; - labelName = "Name"; - labelFieldType = "Field Type"; - labelMultiLine = "Multi-line"; - labelDefaultValue = "Default Value"; - labelMinValue = "Minimum Value"; - labelMaxValue = "Maximum Value"; - labelStep = "Step"; - labelRequired = "Required"; - labelMinEntries = "Min Entries"; - labelMaxEntries = "Max Entries (empty=unlimited)"; - labelRefElementId = "Sequence/Form/Glossary"; + labelName = "Name"; + labelFieldType = "Field Type"; + labelMultiLine = "Multi-line"; + labelDefaultValue = "Default Value"; + labelMinValue = "Minimum Value"; + labelMaxValue = "Maximum Value"; + labelStep = "Step"; + labelRequired = "Required"; + labelMinEntries = "Min Entries"; + labelMaxEntries = "Max Entries (empty=unlimited)"; + labelRefElementId = "Sequence/Form/Glossary"; - labelApply = "Save"; - labelSave = "Save and close"; + labelApply = "Save"; + labelSave = "Save and close"; - schema = { - name: Joi.string().required().max(450).label(this.labelName), - fieldType: Joi.string().required().label(this.labelFieldType), - multiLine: Joi.boolean().label(this.labelMultiLine), - minEntries: Joi.number().min(0).label(this.labelMinEntries), - maxEntries: Joi.number().empty("").label(this.labelMaxEntries), - refElementId: Joi.when("fieldType", { - is: Joi.string().valid("Sequence"), - then: Joi.object({ - id: Joi.optional(), - guid: Joi.optional(), + schema = { + name: Joi.string().required().max(450).label(this.labelName), + fieldType: Joi.string().required().label(this.labelFieldType), + multiLine: Joi.boolean().label(this.labelMultiLine), + minEntries: Joi.number().min(0).label(this.labelMinEntries), + maxEntries: Joi.number().empty("").label(this.labelMaxEntries), + refElementId: Joi.when("fieldType", { + is: Joi.string().valid("Sequence"), + then: Joi.object({ + id: 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(), - }).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(), - }), - 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), + }), + ) + .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", { - is: Joi.string().valid("Number"), - then: Joi.when("minValue", { - is: Joi.any().valid(null, ""), - then: Joi.number(), - otherwise: Joi.number() - .min(Joi.ref("minValue")) - .message('"Default Value" must be greater than or equal to "' + this.labelMinValue + '"'), - }) - .when("maxValue", { - is: Joi.any().valid(null, ""), - then: Joi.number(), - otherwise: Joi.number() - .max(Joi.ref("maxValue")) - .message('"Default Value" must be less than or equal to "' + this.labelMaxValue + '"'), - }) - .allow(""), - otherwise: Joi.string().allow(""), - }).label(this.labelDefaultValue), - }; + defaultValue: Joi.when("fieldType", { + is: Joi.string().valid("Number"), + then: Joi.when("minValue", { + is: Joi.any().valid(null, ""), + then: Joi.number(), + otherwise: Joi.number() + .min(Joi.ref("minValue")) + .message( + '"Default Value" must be greater than or equal to "' + + this.labelMinValue + + '"', + ), + }) + .when("maxValue", { + is: Joi.any().valid(null, ""), + then: Joi.number(), + otherwise: Joi.number() + .max(Joi.ref("maxValue")) + .message( + '"Default Value" must be less than or equal to "' + + this.labelMaxValue + + '"', + ), + }) + .allow(""), + otherwise: Joi.string().allow(""), + }).label(this.labelDefaultValue), + }; - doSubmit = async (buttonName: string) => { - try { - const { name, fieldType } = this.state.data; - let { refElementId, defaultValue, minEntries, maxEntries, required } = this.state.data; - let numberParams: numberParams | undefined = undefined; - let textParams: textParams | undefined = undefined; - let params; - let refElementIdValue: GeneralIdRef | undefined; + doSubmit = async (buttonName: string) => { + try { + const { name, fieldType } = this.state.data; + let { refElementId, defaultValue, minEntries, maxEntries, required } = + this.state.data; + let numberParams: numberParams | undefined = undefined; + let textParams: textParams | undefined = undefined; + let params; + let refElementIdValue: GeneralIdRef | undefined; - switch (fieldType) { - case "Sequence": - minEntries = 1; - maxEntries = 1; - defaultValue = ""; - refElementIdValue = refElementId as GeneralIdRef; - break; - case "FormTemplate": - minEntries = 1; - maxEntries = 1; - defaultValue = ""; - break; - case "Domain": - minEntries = required ? 1 : 0; - maxEntries = maxEntries === 0 ? undefined : maxEntries; - defaultValue = ""; - break; - case "Glossary": - minEntries = required ? 1 : 0; - maxEntries = maxEntries === 0 ? undefined : maxEntries; - defaultValue = ""; - refElementIdValue = (refElementId as CustomFieldValue[])[0].value as GeneralIdRef; - break; - case "Text": - minEntries = 1; - maxEntries = 1; - let { multiLine } = this.state.data; - textParams = { multiLine }; - params = textParams; - refElementIdValue = undefined; - break; - case "Number": - refElementIdValue = undefined; - let { minValue, maxValue, step } = this.state.data; - numberParams = { minValue, maxValue, step }; - params = numberParams; - minEntries = required ? 1 : 0; - maxEntries = 1; - break; - default: - refElementIdValue = undefined; - } + switch (fieldType) { + case "Sequence": + minEntries = 1; + maxEntries = 1; + defaultValue = ""; + refElementIdValue = refElementId as GeneralIdRef; + break; + case "FormTemplate": + minEntries = 1; + maxEntries = 1; + defaultValue = ""; + break; + case "Domain": + minEntries = required ? 1 : 0; + maxEntries = maxEntries === 0 ? undefined : maxEntries; + defaultValue = ""; + break; + case "Glossary": + minEntries = required ? 1 : 0; + maxEntries = maxEntries === 0 ? undefined : maxEntries; + defaultValue = ""; + refElementIdValue = (refElementId as CustomFieldValue[])[0] + .value as GeneralIdRef; + break; + case "Text": + minEntries = 1; + maxEntries = 1; + let { multiLine } = this.state.data; + textParams = { multiLine }; + params = textParams; + refElementIdValue = undefined; + break; + case "Number": + refElementIdValue = undefined; + let { minValue, maxValue, step } = this.state.data; + numberParams = { minValue, maxValue, step }; + params = numberParams; + minEntries = required ? 1 : 0; + maxEntries = 1; + break; + default: + refElementIdValue = undefined; + } - const cleanMaxEntries: Number | undefined = maxEntries === "" ? undefined : Number(maxEntries); + const cleanMaxEntries: Number | undefined = + maxEntries === "" ? undefined : Number(maxEntries); - 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 () => { + if (this.isEditMode()) { 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 ; - - 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 ( - -

{mode} Custom Field

-
- {this.renderError("_general")} - {this.renderInput("name", this.labelName, InputType.text)} - {this.renderSelect("fieldType", this.labelFieldType, fieldTypeOptions)} - - - {this.renderInput("required", this.labelRequired, InputType.checkbox)} - {this.renderInput("maxEntries", this.labelMaxEntries, InputType.number)} - - - {this.renderGlossaryPicker(true, "refElementId", this.labelRefElementId, 1, SystemGlossaries)} - {this.renderInput("required", this.labelRequired, InputType.checkbox)} - {this.renderInput("maxEntries", this.labelMaxEntries, InputType.number)} - - - {this.renderSequencePicker(true, "refElementId", this.labelRefElementId)} - - - <> - - - {this.renderInput("multiLine", this.labelMultiLine, InputType.checkbox)} - {this.renderInputTextarea(true, "defaultValue", this.labelDefaultValue)} - - {this.renderInput("defaultValue", this.labelDefaultValue, InputType.text)} - - - - {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)} - - - {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)} -
-
+ 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; + + 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 ; + + 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 ( + +

{mode} Custom Field

+
+ {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)} +
+
+ ); + } } const HOCCustomFieldDetails = withRouter(CustomFieldDetails);