From 04c1875d5d5d0b05563d789ffa6f35ec5908e49d Mon Sep 17 00:00:00 2001 From: Colin Dawson Date: Fri, 30 Jan 2026 11:30:40 +0000 Subject: [PATCH] Upgraded ConfirmEmail --- public/locales/en/common.json | 7 + .../EmailUserActionConfirmEmail.tsx | 277 ++++++++++-------- 2 files changed, 162 insertions(+), 122 deletions(-) diff --git a/public/locales/en/common.json b/public/locales/en/common.json index 94f9a29..b770e0d 100644 --- a/public/locales/en/common.json +++ b/public/locales/en/common.json @@ -1,5 +1,7 @@ { + "Activate": "Activate", "Admin": "Admin", + "AnErrorOccurred": "An error occurred", "AuditLog": "Audit Logs", "AuditLogs": "Audit Logs", "BlockedIPAddresses": "Blocked IP addresses", @@ -8,6 +10,7 @@ "ClientDomainManager": "Client Domain Manager", "ClientDomains": "Client Domains", "Comment": "Comment", + "ConfirmPassword": "Confirm Password", "CustomFieldManager": "Custom Field Manager", "CustomFields": "Custom Fields", "DisplayName": "Display Name", @@ -35,6 +38,10 @@ "NumberOfAttempts": "Number Of Attempts", "NewValue": "New Value", "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", "Sequence": "Sequence", "SequenceManager": "Sequence Manager", diff --git a/src/modules/frame/components/EmailUserActionConfirmEmail.tsx b/src/modules/frame/components/EmailUserActionConfirmEmail.tsx index 60d7080..b7e0b4f 100644 --- a/src/modules/frame/components/EmailUserActionConfirmEmail.tsx +++ b/src/modules/frame/components/EmailUserActionConfirmEmail.tsx @@ -1,141 +1,174 @@ -import Form, { FormState, FormData, businessValidationResult } from "../../../components/common/Form"; -import Joi from "joi"; +import React, { useState } from "react"; import authentication from "../services/authenticationService"; import { IEmailUserAction } from "../models/IEmailUserAction"; import { InputType } from "../../../components/common/Input"; +import Input from "../../../components/common/Input"; +import Button, { ButtonType } from "../../../components/common/Button"; +import ErrorBlock from "../../../components/common/ErrorBlock"; +import { FormData } from "../../../components/common/Form"; +import { useTranslation } from "react-i18next"; +import { Namespaces } from "../../../i18n/i18n"; export interface EmailUserActionConfirmEmailData extends FormData { - password: string; - confirmPassword: string; - emailConfirmed: boolean; + password: string; + confirmPassword: string; + emailConfirmed: boolean; } -export interface EmailUserActionConfirmEmailState extends FormState { - data: EmailUserActionConfirmEmailData; - hasTwelveCharacters: boolean, - hasSpecialCharacter: boolean, - hasUppercaseLetter: boolean, - hasLowercaseLetter: boolean, - hasNumber: boolean +interface Props { + emailUserAction: IEmailUserAction; } -class EmailUserActionConfirmEmail extends Form { - state = { - loaded: true, - passwordMaxLenght: 255, - data: { password: "", confirmPassword: "", emailConfirmed: false }, - errors: {}, - hasTwelveCharacters: false, - hasSpecialCharacter: false, - hasUppercaseLetter: false, - hasLowercaseLetter: false, - hasNumber: false - }; +const EmailUserActionConfirmEmail: React.FC = ({ emailUserAction }) => { + const { t } = useTranslation(); + const [password, setPassword] = useState(""); + const [confirmPassword, setConfirmPassword] = useState(""); + const [emailConfirmed, setEmailConfirmed] = 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 = "Password"; - labelConfirmPassword = "Confirm Password"; - labelConfirmEmail = "Activate"; + const LABEL_PASSWORD = t("Password"); + const LABEL_CONFIRM_PASSWORD = t("Confirmassword"); + const LABEL_CONFIRM_EMAIL = t("Activate"); + 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); + }; - emailConfirmed: 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", - }, - ], - }; - } - return null; + const minPasswordLength = 12; + + // Validation + if (!password) { + newErrors.password = t("PasswordIsRequired"); + } else if (password.length < minPasswordLength) { + newErrors.password = t("PasswordMinLength", { + minPasswordLength: minPasswordLength, + }); } - handlePasswordChange = async (e: React.ChangeEvent) => { - const stateData = this.state; - stateData.data.password = e.currentTarget.value; - stateData.data.confirmPassword = ""; - stateData.hasNumber = /\d+/g.test(stateData.data.password); - stateData.hasLowercaseLetter = /[a-z]/g.test(stateData.data.password); - stateData.hasUppercaseLetter = /[A-Z]/g.test(stateData.data.password);; - stateData.hasSpecialCharacter = /[ ~`! @#$%^&*()_+\-=[\]{};:\\|,.'"<>/?]/.test(stateData.data.password); - stateData.hasTwelveCharacters = stateData.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.emailConfirmed = true; - this.setState({ data }); - setTimeout(function () { - window.location.replace('/login'); - }, 1000); - } - } - catch(ex: any) { - this.handleGeneralError(ex); - } - }; - - render() { - const { emailConfirmed, 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 (emailConfirmed) { - return
Success, your e-mail is confirmed. You can now log in.
; - } - - return ( - <> -
To activate your account, please enter a password
- -
- {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.labelConfirmEmail, "confirmEmail", 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) { + setEmailConfirmed(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 (emailConfirmed) { + return ( +
+ Success, your e-mail is confirmed. You can now log in. +
+ ); + } + + return ( + <> +
To activate your account, please enter a password
+ +
+ {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 EmailUserActionConfirmEmail;