From 2b476a48dbe5a80fc5d42f81ec0e8142772aa9ea Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 14:38:26 +0530 Subject: [PATCH 01/28] show validations upfront --- public/locale/en.json | 25 +++--- src/components/Auth/ResetPassword.tsx | 74 +++++++---------- src/components/Users/CreateUserForm.tsx | 85 +++++++++++++++++--- src/components/Users/UserFormValidations.tsx | 50 ++++++++++++ src/components/Users/UserResetPassword.tsx | 63 +++++++-------- 5 files changed, 196 insertions(+), 101 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 10486c83b99..31ba88c76d2 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -1352,8 +1352,7 @@ "never_logged_in": "Never Logged In", "new_password": "New Password", "new_password_confirmation": "Confirm New Password", - "new_password_different_from_old": "Your new password is different from the old password.", - "new_password_same_as_old": "Your new password must not match the old password.", + "new_password_same_as_old": "Your new password must not match the old password ", "new_password_validation": "New password is not valid.", "new_session": "New Session", "next_month": "Next month", @@ -1488,21 +1487,18 @@ "pain_chart_description": "Mark region and intensity of pain", "passport_number": "Passport Number", "password": "Password", - "password_length_met": "It's at least 8 characters long", - "password_length_validation": "Use at least 8 characters", - "password_lowercase_met": "It includes at least one lowercase letter", - "password_lowercase_validation": "Include at least one lowercase letter", + "password_length_validation": "Use at least 8 characters", + "password_lowercase_validation": "Include at least one lowercase letter (a-z)", "password_mismatch": "Passwords do not match", - "password_number_met": "It includes at least one number.", - "password_number_validation": "Include at least one number.", + "password_number_validation": "Include at least one number (0-9)", "password_required": "Password is required", "password_reset_failure": "Password Reset Failed", "password_reset_success": "Password Reset successfully", "password_sent": "Password Reset Email Sent", + "password_success_message": "All set! Your password is strong", "password_update_error": "Error while updating password. Try again later.", "password_updated": "Password updated successfully", - "password_uppercase_met": "It includes at least one uppercase letter.", - "password_uppercase_validation": "Include at least one uppercase letter.", + "password_uppercase_validation": "Include at least one uppercase letter (A-Z).", "passwords_match": "Passwords match.", "patient": "Patient", "patient-notes": "Notes", @@ -2149,12 +2145,13 @@ "username": "Username", "username_already_exists": "This username already exists", "username_available": "Username is available", - "username_characters_validation": "Only lowercase letters, numbers, and . _ - are allowed", - "username_consecutive_validation": "Cannot contain consecutive special characters", - "username_max_length_validation": "Use at most 16 characters", + "username_characters_validation": "Only lowercase letters, numbers, and . _ - are allowed", + "username_consecutive_validation": "Cannot contain consecutive special characters", + "username_max_length_validation": "Use at most 16 characters", "username_min_length_validation": "Use at least 4 characters", "username_not_available": "Username is not available", - "username_start_end_validation": "Must start and end with a letter or number", + "username_start_end_validation": "Must start and end with a letter or number", + "username_success_message": "All set! Your username is strong", "username_userdetails_not_found": "Unable to fetch details as username or user details not found", "username_valid": "Username is valid", "users": "Users", diff --git a/src/components/Auth/ResetPassword.tsx b/src/components/Auth/ResetPassword.tsx index 273e5486140..1f75e5a1ff6 100644 --- a/src/components/Auth/ResetPassword.tsx +++ b/src/components/Auth/ResetPassword.tsx @@ -6,7 +6,7 @@ import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { PasswordInput } from "@/components/ui/input-password"; -import { validateRule } from "@/components/Users/UserFormValidations"; +import { ValidationHelper } from "@/components/Users/UserFormValidations"; import { LocalStorageKeys } from "@/common/constants"; import { validatePassword } from "@/common/validation"; @@ -27,9 +27,7 @@ const ResetPassword = (props: ResetPasswordProps) => { const initErr: any = {}; const [form, setForm] = useState(initForm); const [errors, setErrors] = useState(initErr); - const [passwordInputInFocus, setPasswordInputInFocus] = useState(false); - const [confirmPasswordInputInFocus, setConfirmPasswordInputInFocus] = - useState(false); + const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); const { t } = useTranslation(); const handleChange = (e: any) => { @@ -124,40 +122,41 @@ const ResetPassword = (props: ResetPasswordProps) => { name="password" placeholder={t("new_password")} onChange={handleChange} - onFocus={() => setPasswordInputInFocus(true)} - onBlur={() => setPasswordInputInFocus(false)} + onFocus={() => setIsPasswordFieldFocused(true)} + onBlur={() => setIsPasswordFieldFocused(false)} /> {errors.password && (
{errors.password}
)} - {passwordInputInFocus && ( -
- {validateRule( - form.password?.length >= 8, - t("password_length_validation"), - !form.password, - t("password_length_met"), - )} - {validateRule( - form.password !== form.password.toUpperCase(), - t("password_lowercase_validation"), - !form.password, - t("password_lowercase_met"), - )} - {validateRule( - form.password !== form.password.toLowerCase(), - t("password_uppercase_validation"), - !form.password, - t("password_uppercase_met"), - )} - {validateRule( - /\d/.test(form.password), - t("password_number_validation"), - !form.password, - t("password_number_met"), - )} + {isPasswordFieldFocused && ( +
+ = 8, + }, + { + description: "password_lowercase_validation", + fulfilled: /[a-z]/.test(form.password), + }, + { + description: "password_uppercase_validation", + fulfilled: /[A-Z]/.test(form.password), + }, + { + description: "password_number_validation", + fulfilled: /\d/.test(form.password), + }, + ]} + />
)}
@@ -167,23 +166,12 @@ const ResetPassword = (props: ResetPasswordProps) => { name="confirm" placeholder={t("confirm_password")} onChange={handleChange} - onFocus={() => setConfirmPasswordInputInFocus(true)} - onBlur={() => setConfirmPasswordInputInFocus(false)} /> {errors.confirm && (
{errors.confirm}
)} - {confirmPasswordInputInFocus && - form.confirm.length > 0 && - form.password.length > 0 && - validateRule( - form.confirm === form.password, - t("password_mismatch"), - !form.password && form.password.length > 0, - t("password_match"), - )} diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index 81d4851a0df..ab42a3e9da6 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -1,6 +1,6 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useQuery } from "@tanstack/react-query"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -28,7 +28,10 @@ import { SelectValue, } from "@/components/ui/select"; -import { validateRule } from "@/components/Users/UserFormValidations"; +import { + ValidationHelper, + validateRule, +} from "@/components/Users/UserFormValidations"; import { GENDER_TYPES } from "@/common/constants"; @@ -102,6 +105,9 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { }, }); + const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); + const [isUsernameFieldFocused, setIsUsernameFieldFocused] = useState(false); + const userType = form.watch("user_type"); const usernameInput = form.watch("username"); const phoneNumber = form.watch("phone_number"); @@ -130,14 +136,8 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { errors: { username }, } = form.formState; const isInitialRender = usernameInput === ""; - if (username?.message) { - return validateRule( - false, - username.message, - isInitialRender, - t("username_valid"), - ); + return null; } else if (isUsernameChecking) { return (
@@ -261,9 +261,44 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { data-cy="username-input" placeholder={t("username")} {...field} + onFocus={() => setIsUsernameFieldFocused(true)} + onBlur={() => setIsUsernameFieldFocused(false)} />
+ {isUsernameFieldFocused && ( +
+ = 4, + }, + { + description: "username_max_length_validation", + fulfilled: field.value.length <= 16, + }, + { + description: "username_characters_validation", + fulfilled: /^[a-z0-9._-]*$/.test(field.value), + }, + { + description: "username_start_end_validation", + fulfilled: /^[a-z0-9].*[a-z0-9]$/.test(field.value), + }, + { + description: "username_consecutive_validation", + fulfilled: !/(?:[._-]{2,})/.test(field.value), + }, + ]} + /> +
+ )} {renderUsernameFeedback(usernameInput)} )} @@ -281,9 +316,39 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { data-cy="password-input" placeholder={t("password")} {...field} + onFocus={() => setIsPasswordFieldFocused(true)} + onBlur={() => setIsPasswordFieldFocused(false)} /> - + {isPasswordFieldFocused && ( +
+ = 8, + }, + { + description: "password_lowercase_validation", + fulfilled: /[a-z]/.test(field.value), + }, + { + description: "password_uppercase_validation", + fulfilled: /[A-Z]/.test(field.value), + }, + { + description: "password_number_validation", + fulfilled: /\d/.test(field.value), + }, + ]} + /> +
+ )} )} /> diff --git a/src/components/Users/UserFormValidations.tsx b/src/components/Users/UserFormValidations.tsx index 4899a6f3d25..f07e08a9718 100644 --- a/src/components/Users/UserFormValidations.tsx +++ b/src/components/Users/UserFormValidations.tsx @@ -1,3 +1,5 @@ +import { Trans } from "react-i18next"; + import CareIcon from "@/CAREUI/icons/CareIcon"; import { classNames } from "@/Utils/utils"; @@ -6,6 +8,54 @@ export type UserType = "doctor" | "nurse" | "staff" | "volunteer"; export type Gender = "male" | "female" | "non_binary" | "transgender"; +type Validation = { + description: string; + fulfilled: boolean; +}; + +type ValidationHelperProps = { + validations: Validation[]; + successMessage: string; + isInputEmpty: boolean; +}; +export const ValidationHelper = ({ + validations, + successMessage, + isInputEmpty, +}: ValidationHelperProps) => { + const unfulfilledValidations = validations.filter( + (validation) => !validation.fulfilled, + ); + + const allValid = unfulfilledValidations.length === 0 && !isInputEmpty; + + return ( +
+ {isInputEmpty && + validations.map((validation, index) => ( +
+ +
+ ))} + {!isInputEmpty && + !allValid && + unfulfilledValidations.map((validation, index) => ( +
+ +
+ ))} + {allValid && ( + <> + + + {successMessage} + + + )} +
+ ); +}; + export const validateRule = ( isConditionMet: boolean, initialMessage: JSX.Element | string, diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 5a706832e25..f4682b5a817 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -19,7 +19,7 @@ import { } from "@/components/ui/form"; import { PasswordInput } from "@/components/ui/input-password"; -import { validateRule } from "@/components/Users/UserFormValidations"; +import { ValidationHelper } from "@/components/Users/UserFormValidations"; import { UpdatePasswordForm } from "@/components/Users/models"; import routes from "@/Utils/request/api"; @@ -147,44 +147,39 @@ export default function UserResetPassword({ onBlur={() => setIsPasswordFieldFocused(false)} /> - {isPasswordFieldFocused ? ( + {isPasswordFieldFocused && (
- {validateRule( - field.value.length >= 8, - t("password_length_validation"), - !field.value, - t("password_length_met"), - )} - {validateRule( - /[a-z]/.test(field.value), - t("password_lowercase_validation"), - !field.value, - t("password_lowercase_met"), - )} - {validateRule( - /[A-Z]/.test(field.value), - t("password_uppercase_validation"), - !field.value, - t("password_uppercase_met"), - )} - {validateRule( - /\d/.test(field.value), - t("password_number_validation"), - !field.value, - t("password_number_met"), - )} - {validateRule( - field.value !== form.watch("old_password"), - t("new_password_same_as_old"), - !field.value, - t("new_password_different_from_old"), - )} + = 8, + }, + { + description: "password_lowercase_validation", + fulfilled: /[a-z]/.test(field.value), + }, + { + description: "password_uppercase_validation", + fulfilled: /[A-Z]/.test(field.value), + }, + { + description: "password_number_validation", + fulfilled: /\d/.test(field.value), + }, + { + description: "new_password_same_as_old", + fulfilled: + field.value !== form.watch("old_password"), + }, + ]} + />
- ) : ( - )} )} From d22de896a2872bf1228174db58085eebadbc94ec Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 15:01:01 +0530 Subject: [PATCH 02/28] typo error --- public/locale/en.json | 2 +- src/components/Users/CreateUserForm.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 31ba88c76d2..f3094b617a0 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -2148,7 +2148,7 @@ "username_characters_validation": "Only lowercase letters, numbers, and . _ - are allowed", "username_consecutive_validation": "Cannot contain consecutive special characters", "username_max_length_validation": "Use at most 16 characters", - "username_min_length_validation": "Use at least 4 characters", + "username_min_length_validation": "Use at least 4 characters", "username_not_available": "Username is not available", "username_start_end_validation": "Must start and end with a letter or number", "username_success_message": "All set! Your username is strong", diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index ab42a3e9da6..88d4aba71f9 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -276,7 +276,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { successMessage={t("username_success_message")} validations={[ { - description: "password_length_validation", + description: "username_min_length_validation", fulfilled: field.value.length >= 4, }, { From efa143099d86355938658dfe97c9c7df6a97a8ac Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 15:51:21 +0530 Subject: [PATCH 03/28] cypress fail test --- cypress/pageObject/Users/UserCreation.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index 181ad9d0649..99cccc98673 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -35,12 +35,20 @@ export class UserCreation { } fillUsername(username: string) { - cy.typeIntoField('[data-cy="username-input"]', username); + cy.get('[data-cy="username-input"]') + .should("exist") + .should("be.visible") + .clear() + .type(username, { force: true }); return this; } fillPassword(password: string) { - cy.typeIntoField('[data-cy="password-input"]', password); + cy.get('[data-cy="password-input"]') + .should("exist") // Ensure the field exists + .should("be.visible") // Ensure the field is visible + .clear() // Clear the field before typing + .type(password, { force: true }); // Type the password return this; } From 39d317d9b4d45d240e20dd80b49fe5b1a7600942 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 15:58:22 +0530 Subject: [PATCH 04/28] reset cypress file changes --- cypress/pageObject/Users/UserCreation.ts | 12 ++---------- package.json | 2 +- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index 99cccc98673..181ad9d0649 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -35,20 +35,12 @@ export class UserCreation { } fillUsername(username: string) { - cy.get('[data-cy="username-input"]') - .should("exist") - .should("be.visible") - .clear() - .type(username, { force: true }); + cy.typeIntoField('[data-cy="username-input"]', username); return this; } fillPassword(password: string) { - cy.get('[data-cy="password-input"]') - .should("exist") // Ensure the field exists - .should("be.visible") // Ensure the field is visible - .clear() // Clear the field before typing - .type(password, { force: true }); // Type the password + cy.typeIntoField('[data-cy="password-input"]', password); return this; } diff --git a/package.json b/package.json index 96b8f0675f8..8e2638e3e06 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "author": "Open Healthcare Network Contributors", "license": "MIT", "scripts": { - "dev": "npm run supported-browsers && vite", + "dev": "npm run supported-browsers && vite --host", "local": "npm run supported-browsers && vite --mode docker", "preview": "cross-env NODE_ENV=production vite preview", "build:meta": "node ./scripts/generate-build-version.js", From d0dec71d7a003d0e610af0fe0a2651921c4db95e Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 16:57:24 +0530 Subject: [PATCH 05/28] cypress fail test --- cypress/pageObject/Users/UserCreation.ts | 5 +++-- package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index 181ad9d0649..bb28b91b68a 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -35,12 +35,13 @@ export class UserCreation { } fillUsername(username: string) { - cy.typeIntoField('[data-cy="username-input"]', username); + // cy.typeIntoField('[data-cy="username-input"]', username); + cy.get('[data-cy="username-input"]').click().type(username); return this; } fillPassword(password: string) { - cy.typeIntoField('[data-cy="password-input"]', password); + cy.get('[data-cy="password-input"]').click().type(password); return this; } diff --git a/package.json b/package.json index 8e2638e3e06..96b8f0675f8 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "author": "Open Healthcare Network Contributors", "license": "MIT", "scripts": { - "dev": "npm run supported-browsers && vite --host", + "dev": "npm run supported-browsers && vite", "local": "npm run supported-browsers && vite --mode docker", "preview": "cross-env NODE_ENV=production vite preview", "build:meta": "node ./scripts/generate-build-version.js", From 3154f3fdddda8738cc4734ff25d751aebaa198a8 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 17:03:51 +0530 Subject: [PATCH 06/28] cypress fail test --- cypress/pageObject/Users/UserCreation.ts | 25 +++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index bb28b91b68a..c90784abb6c 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -36,12 +36,31 @@ export class UserCreation { fillUsername(username: string) { // cy.typeIntoField('[data-cy="username-input"]', username); - cy.get('[data-cy="username-input"]').click().type(username); - return this; + cy.get('[data-cy="username-input"]') + .should("exist") // Ensure the input exists + .should("be.visible") // Ensure it's visible + .click() // Focus on the input + .clear() // Clear any existing value + .type(username, { delay: 50 }); // Type the username with a slight delay + + // Optional: Wait for validation to settle + cy.wait(500); + + // Verify the input has the correct value + cy.get('[data-cy="username-input"]').should("have.value", username); } fillPassword(password: string) { - cy.get('[data-cy="password-input"]').click().type(password); + cy.get('[data-cy="password-input"]') + .should("exist") // Ensure the input exists in the DOM + .should("be.visible") // Ensure it's visible + .click() // Focus on the input + .clear() // Clear any pre-existing value + .type(password, { delay: 50 }); // Type the password with a slight delay + + // Optional: Validate that the password field contains the correct value + cy.get('[data-cy="password-input"]').should("have.value", password); + return this; } From 52a6e87a09cfa22ced96332ca2ada1b0b8aa56f0 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 17:18:05 +0530 Subject: [PATCH 07/28] cypress fail test --- cypress/pageObject/Users/UserCreation.ts | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index c90784abb6c..f49c78eeb1c 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -35,32 +35,32 @@ export class UserCreation { } fillUsername(username: string) { - // cy.typeIntoField('[data-cy="username-input"]', username); cy.get('[data-cy="username-input"]') - .should("exist") // Ensure the input exists - .should("be.visible") // Ensure it's visible - .click() // Focus on the input - .clear() // Clear any existing value - .type(username, { delay: 50 }); // Type the username with a slight delay - - // Optional: Wait for validation to settle + .should("exist") + .should("be.visible") + .click(); + cy.get('[data-cy="username-input"]') + .should("exist") + .should("be.visible") + .clear() + .type(username, { delay: 50 }); cy.wait(500); - - // Verify the input has the correct value cy.get('[data-cy="username-input"]').should("have.value", username); + return this; } fillPassword(password: string) { cy.get('[data-cy="password-input"]') .should("exist") // Ensure the input exists in the DOM .should("be.visible") // Ensure it's visible - .click() // Focus on the input - .clear() // Clear any pre-existing value - .type(password, { delay: 50 }); // Type the password with a slight delay - - // Optional: Validate that the password field contains the correct value + .click(); // Focus on the input + cy.get('[data-cy="password-input"]') + .should("exist") + .should("be.visible") + .clear() + .type(password, { delay: 50 }); + cy.wait(500); cy.get('[data-cy="password-input"]').should("have.value", password); - return this; } From 09668e4a1e7eb84e38446dc0902650870073b67c Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 17:22:44 +0530 Subject: [PATCH 08/28] Empty-Commit From 589e3a8ecdf19f97d5e7d4a6ef443f7c245c5d81 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 17:28:40 +0530 Subject: [PATCH 09/28] cypress fail test --- cypress/pageObject/Users/UserCreation.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index f49c78eeb1c..076fb0be849 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -35,32 +35,38 @@ export class UserCreation { } fillUsername(username: string) { - cy.get('[data-cy="username-input"]') + cy.get('[data-cy="username-input"]', { timeout: 20000 }) .should("exist") .should("be.visible") .click(); - cy.get('[data-cy="username-input"]') + cy.get('[data-cy="username-input"]', { timeout: 20000 }) .should("exist") .should("be.visible") .clear() .type(username, { delay: 50 }); cy.wait(500); - cy.get('[data-cy="username-input"]').should("have.value", username); + cy.get('[data-cy="username-input"]', { timeout: 20000 }).should( + "have.value", + username, + ); return this; } fillPassword(password: string) { - cy.get('[data-cy="password-input"]') + cy.get('[data-cy="password-input"]', { timeout: 20000 }) .should("exist") // Ensure the input exists in the DOM .should("be.visible") // Ensure it's visible .click(); // Focus on the input - cy.get('[data-cy="password-input"]') + cy.get('[data-cy="password-input"]', { timeout: 20000 }) .should("exist") .should("be.visible") .clear() .type(password, { delay: 50 }); cy.wait(500); - cy.get('[data-cy="password-input"]').should("have.value", password); + cy.get('[data-cy="password-input"]', { timeout: 20000 }).should( + "have.value", + password, + ); return this; } From 7900659aa26b37ba282758074dc08f6b2ce4ab65 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 17:38:42 +0530 Subject: [PATCH 10/28] Empty-Commit From 9d62331ad0e31e48e7446bc8bf7faa00f9f2f136 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 17:44:56 +0530 Subject: [PATCH 11/28] reset cypress changes --- cypress/pageObject/Users/UserCreation.ts | 30 ++---------------------- 1 file changed, 2 insertions(+), 28 deletions(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index 076fb0be849..181ad9d0649 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -35,38 +35,12 @@ export class UserCreation { } fillUsername(username: string) { - cy.get('[data-cy="username-input"]', { timeout: 20000 }) - .should("exist") - .should("be.visible") - .click(); - cy.get('[data-cy="username-input"]', { timeout: 20000 }) - .should("exist") - .should("be.visible") - .clear() - .type(username, { delay: 50 }); - cy.wait(500); - cy.get('[data-cy="username-input"]', { timeout: 20000 }).should( - "have.value", - username, - ); + cy.typeIntoField('[data-cy="username-input"]', username); return this; } fillPassword(password: string) { - cy.get('[data-cy="password-input"]', { timeout: 20000 }) - .should("exist") // Ensure the input exists in the DOM - .should("be.visible") // Ensure it's visible - .click(); // Focus on the input - cy.get('[data-cy="password-input"]', { timeout: 20000 }) - .should("exist") - .should("be.visible") - .clear() - .type(password, { delay: 50 }); - cy.wait(500); - cy.get('[data-cy="password-input"]', { timeout: 20000 }).should( - "have.value", - password, - ); + cy.typeIntoField('[data-cy="password-input"]', password); return this; } From b5debd482b4e1168ca59bb8ca508a884435189c2 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 19:46:29 +0530 Subject: [PATCH 12/28] coderabbit suggestion --- cypress/pageObject/Users/UserCreation.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index 181ad9d0649..15bcdd22d30 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -35,12 +35,17 @@ export class UserCreation { } fillUsername(username: string) { - cy.typeIntoField('[data-cy="username-input"]', username); + cy.get('[data-cy="username-input"]') + .should("be.visible") + .type(username, { force: true }); return this; } fillPassword(password: string) { - cy.typeIntoField('[data-cy="password-input"]', password); + cy.get('[data-cy="password-input"]') + .should("be.visible") + .type(password, { force: true }); + return this; } From 5e0afac1ea9cbd6ca929916a412fc2967006ae9e Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 20:11:17 +0530 Subject: [PATCH 13/28] coderabbit suggestion --- cypress/pageObject/Users/UserCreation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index 15bcdd22d30..457f356a48b 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -42,7 +42,7 @@ export class UserCreation { } fillPassword(password: string) { - cy.get('[data-cy="password-input"]') + cy.get('[data-cy="password-input"]', { timeout: 15000 }) .should("be.visible") .type(password, { force: true }); From 8845799ddc10ad269520fdb7c34c2d00130f222e Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 20:35:23 +0530 Subject: [PATCH 14/28] coderabbit suggestion --- cypress/pageObject/Users/UserCreation.ts | 27 ++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index 457f356a48b..081737e4199 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -35,16 +35,31 @@ export class UserCreation { } fillUsername(username: string) { - cy.get('[data-cy="username-input"]') - .should("be.visible") - .type(username, { force: true }); + [...username].forEach((char) => { + cy.get('[data-cy="username-input"]') + .should("exist") // Ensure the input exists + .should("be.visible") // Ensure it's visible + .click() // Focus on the input + .type(char); // Type one character at a time + }); + + // Validate the final input value after typing all characters + cy.get('[data-cy="username-input"]').should("have.value", username); + return this; } fillPassword(password: string) { - cy.get('[data-cy="password-input"]', { timeout: 15000 }) - .should("be.visible") - .type(password, { force: true }); + [...password].forEach((char) => { + cy.get('[data-cy="password-input"]') + .should("exist") // Ensure the input exists + .should("be.visible") // Ensure it's visible + .click() // Focus on the input + .type(char); // Type one character at a time + }); + + // Validate the final input value after typing all characters + cy.get('[data-cy="password-input"]').should("have.value", password); return this; } From 7cf34b59419b637a2104894941747cc313b0b7cd Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sun, 19 Jan 2025 20:41:04 +0530 Subject: [PATCH 15/28] reset cypress changes --- cypress/pageObject/Users/UserCreation.ts | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/cypress/pageObject/Users/UserCreation.ts b/cypress/pageObject/Users/UserCreation.ts index 081737e4199..181ad9d0649 100644 --- a/cypress/pageObject/Users/UserCreation.ts +++ b/cypress/pageObject/Users/UserCreation.ts @@ -35,32 +35,12 @@ export class UserCreation { } fillUsername(username: string) { - [...username].forEach((char) => { - cy.get('[data-cy="username-input"]') - .should("exist") // Ensure the input exists - .should("be.visible") // Ensure it's visible - .click() // Focus on the input - .type(char); // Type one character at a time - }); - - // Validate the final input value after typing all characters - cy.get('[data-cy="username-input"]').should("have.value", username); - + cy.typeIntoField('[data-cy="username-input"]', username); return this; } fillPassword(password: string) { - [...password].forEach((char) => { - cy.get('[data-cy="password-input"]') - .should("exist") // Ensure the input exists - .should("be.visible") // Ensure it's visible - .click() // Focus on the input - .type(char); // Type one character at a time - }); - - // Validate the final input value after typing all characters - cy.get('[data-cy="password-input"]').should("have.value", password); - + cy.typeIntoField('[data-cy="password-input"]', password); return this; } From 4a3da834088c5cccf1f4df3b683b8adf2c2396dc Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Mon, 20 Jan 2025 18:36:56 +0530 Subject: [PATCH 16/28] Empty-Commit From 75b9c94ed5402ec7e4c79d3ebc9ba44785c89686 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Tue, 21 Jan 2025 20:54:51 +0530 Subject: [PATCH 17/28] css change --- src/components/Users/CreateUserForm.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index ef5b3b8f820..b29885798c0 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -268,7 +268,7 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { {isUsernameFieldFocused && (
)} - {renderUsernameFeedback(usernameInput)} +
+ {renderUsernameFeedback(usernameInput)} +
)} /> From aaba4c7019ecf287e2f3294834eb42104d7d9493 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Tue, 21 Jan 2025 21:38:46 +0530 Subject: [PATCH 18/28] add default value --- src/components/Users/CreateUserForm.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index b29885798c0..0c6d0cd4861 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -98,6 +98,12 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { resolver: zodResolver(userFormSchema), defaultValues: { user_type: "staff", + username: "", + password: "", + c_password: "", + first_name: "", + last_name: "", + email: "", phone_number: "+91", alt_phone_number: "+91", phone_number_is_whatsapp: true, From 80b89ec15173c0958f316b55881ad3e67691aa9c Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Tue, 21 Jan 2025 21:43:12 +0530 Subject: [PATCH 19/28] check username only on focus --- src/components/Users/CreateUserForm.tsx | 70 +++++++++++++------------ 1 file changed, 36 insertions(+), 34 deletions(-) diff --git a/src/components/Users/CreateUserForm.tsx b/src/components/Users/CreateUserForm.tsx index 0c6d0cd4861..65b4187e368 100644 --- a/src/components/Users/CreateUserForm.tsx +++ b/src/components/Users/CreateUserForm.tsx @@ -273,41 +273,43 @@ export default function CreateUserForm({ onSubmitSuccess }: Props) { {isUsernameFieldFocused && ( -
- = 4, - }, - { - description: "username_max_length_validation", - fulfilled: field.value.length <= 16, - }, - { - description: "username_characters_validation", - fulfilled: /^[a-z0-9._-]*$/.test(field.value), - }, - { - description: "username_start_end_validation", - fulfilled: /^[a-z0-9].*[a-z0-9]$/.test(field.value), - }, - { - description: "username_consecutive_validation", - fulfilled: !/(?:[._-]{2,})/.test(field.value), - }, - ]} - /> -
+ <> +
+ = 4, + }, + { + description: "username_max_length_validation", + fulfilled: field.value.length <= 16, + }, + { + description: "username_characters_validation", + fulfilled: /^[a-z0-9._-]*$/.test(field.value), + }, + { + description: "username_start_end_validation", + fulfilled: /^[a-z0-9].*[a-z0-9]$/.test(field.value), + }, + { + description: "username_consecutive_validation", + fulfilled: !/(?:[._-]{2,})/.test(field.value), + }, + ]} + /> +
+
+ {renderUsernameFeedback(usernameInput)} +
+ )} -
- {renderUsernameFeedback(usernameInput)} -
)} /> From c61d916338ce12ac66107b85c055297fdc0c9219 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Wed, 22 Jan 2025 12:37:28 +0530 Subject: [PATCH 20/28] resolved conflicts --- src/components/Users/UserForm.tsx | 102 +++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 10 deletions(-) diff --git a/src/components/Users/UserForm.tsx b/src/components/Users/UserForm.tsx index b0750393764..7ce684f6500 100644 --- a/src/components/Users/UserForm.tsx +++ b/src/components/Users/UserForm.tsx @@ -1,6 +1,6 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; -import { useEffect } from "react"; +import { useEffect, useState } from "react"; import { useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; @@ -28,7 +28,10 @@ import { SelectValue, } from "@/components/ui/select"; -import { validateRule } from "@/components/Users/UserFormValidations"; +import { + ValidationHelper, + validateRule, +} from "@/components/Users/UserFormValidations"; import { GENDER_TYPES } from "@/common/constants"; import { GENDERS } from "@/common/constants"; @@ -115,6 +118,12 @@ export default function UserForm({ onSubmitSuccess, existingUsername }: Props) { resolver: zodResolver(userFormSchema), defaultValues: { user_type: "staff", + username: "", + password: "", + c_password: "", + first_name: "", + last_name: "", + email: "", phone_number: "+91", alt_phone_number: "+91", phone_number_is_whatsapp: true, @@ -128,7 +137,6 @@ export default function UserForm({ onSubmitSuccess, existingUsername }: Props) { }), enabled: !!existingUsername, }); - useEffect(() => { if (userData && isEditMode) { const formData: Partial = { @@ -144,6 +152,9 @@ export default function UserForm({ onSubmitSuccess, existingUsername }: Props) { } }, [userData, form, isEditMode]); + const [isPasswordFieldFocused, setIsPasswordFieldFocused] = useState(false); + const [isUsernameFieldFocused, setIsUsernameFieldFocused] = useState(false); + //const userType = form.watch("user_type"); const usernameInput = form.watch("username"); const phoneNumber = form.watch("phone_number"); @@ -174,12 +185,7 @@ export default function UserForm({ onSubmitSuccess, existingUsername }: Props) { const isInitialRender = usernameInput === ""; if (username?.message) { - return validateRule( - false, - username.message, - isInitialRender, - t("username_valid"), - ); + return null; } else if (isUsernameChecking) { return (
@@ -347,10 +353,55 @@ export default function UserForm({ onSubmitSuccess, existingUsername }: Props) { data-cy="username-input" placeholder={t("username")} {...field} + onFocus={() => setIsUsernameFieldFocused(true)} + onBlur={() => setIsUsernameFieldFocused(false)} />
- {renderUsernameFeedback(usernameInput ?? "")} + {isUsernameFieldFocused && ( + <> +
+ = 4, + }, + { + description: "username_max_length_validation", + fulfilled: (field.value || "").length <= 16, + }, + { + description: "username_characters_validation", + fulfilled: /^[a-z0-9._-]*$/.test( + field.value || "", + ), + }, + { + description: "username_start_end_validation", + fulfilled: /^[a-z0-9].*[a-z0-9]$/.test( + field.value || "", + ), + }, + { + description: "username_consecutive_validation", + fulfilled: !/(?:[._-]{2,})/.test( + field.value || "", + ), + }, + ]} + /> +
+
+ {renderUsernameFeedback(usernameInput || "")} +
+ + )} )} /> @@ -367,8 +418,39 @@ export default function UserForm({ onSubmitSuccess, existingUsername }: Props) { data-cy="password-input" placeholder={t("password")} {...field} + onFocus={() => setIsPasswordFieldFocused(true)} + onBlur={() => setIsPasswordFieldFocused(false)} /> + {isPasswordFieldFocused && ( +
+ = 8, + }, + { + description: "password_lowercase_validation", + fulfilled: /[a-z]/.test(field.value || ""), + }, + { + description: "password_uppercase_validation", + fulfilled: /[A-Z]/.test(field.value || ""), + }, + { + description: "password_number_validation", + fulfilled: /\d/.test(field.value || ""), + }, + ]} + /> +
+ )} )} From 14cce3323aaf82e46dc2fc58165f45a03ac447cb Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Thu, 23 Jan 2025 20:48:38 +0530 Subject: [PATCH 21/28] optional formMessage --- public/locale/en.json | 1 + src/components/Users/UserForm.tsx | 27 +++++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/public/locale/en.json b/public/locale/en.json index 890d3e6c3f2..e8b1a371ef4 100644 --- a/public/locale/en.json +++ b/public/locale/en.json @@ -2201,6 +2201,7 @@ "username_max_length_validation": "Use at most 16 characters", "username_min_length_validation": "Use at least 4 characters", "username_not_available": "Username is not available", + "username_not_valid": "username is not valid", "username_start_end_validation": "Must start and end with a letter or number", "username_success_message": "All set! Your username is strong", "username_userdetails_not_found": "Unable to fetch details as username or user details not found", diff --git a/src/components/Users/UserForm.tsx b/src/components/Users/UserForm.tsx index 7ce684f6500..7da71aadaaa 100644 --- a/src/components/Users/UserForm.tsx +++ b/src/components/Users/UserForm.tsx @@ -61,22 +61,22 @@ export default function UserForm({ onSubmitSuccess, existingUsername }: Props) { ? z.string().optional() : z .string() - .min(4, t("username_min_length_validation")) - .max(16, t("username_max_length_validation")) - .regex(/^[a-z0-9._-]*$/, t("username_characters_validation")) - .regex(/^[a-z0-9].*[a-z0-9]$/, t("username_start_end_validation")) + .min(4, t("field_required")) + .max(16, t("username_not_valid")) + .regex(/^[a-z0-9._-]*$/, t("username_not_valid")) + .regex(/^[a-z0-9].*[a-z0-9]$/, t("username_not_valid")) .refine( (val) => !val.match(/(?:[._-]{2,})/), - t("username_consecutive_validation"), + t("username_not_valid"), ), password: isEditMode ? z.string().optional() : z .string() - .min(8, t("password_length_validation")) - .regex(/[a-z]/, t("password_lowercase_validation")) - .regex(/[A-Z]/, t("password_uppercase_validation")) - .regex(/[0-9]/, t("password_number_validation")), + .min(8, t("field_required")) + .regex(/[a-z]/, t("new_password_validation")) + .regex(/[A-Z]/, t("new_password_validation")) + .regex(/[0-9]/, t("new_password_validation")), c_password: isEditMode ? z.string().optional() : z.string(), first_name: z.string().min(1, t("field_required")), last_name: z.string().min(1, t("field_required")), @@ -358,7 +358,7 @@ export default function UserForm({ onSubmitSuccess, existingUsername }: Props) { /> - {isUsernameFieldFocused && ( + {isUsernameFieldFocused ? ( <>
+ ) : ( + )} )} @@ -422,7 +424,7 @@ export default function UserForm({ onSubmitSuccess, existingUsername }: Props) { onBlur={() => setIsPasswordFieldFocused(false)} /> - {isPasswordFieldFocused && ( + {isPasswordFieldFocused ? (
+ ) : ( + )} - )} /> From b73eef32e616ee3ad71186ba8fdd0025e9cd2c1e Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Thu, 23 Jan 2025 21:57:13 +0530 Subject: [PATCH 22/28] optional formMessage 2.0 --- src/components/Users/UserResetPassword.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/Users/UserResetPassword.tsx b/src/components/Users/UserResetPassword.tsx index 30f3e818214..fed51b5d5b3 100644 --- a/src/components/Users/UserResetPassword.tsx +++ b/src/components/Users/UserResetPassword.tsx @@ -147,7 +147,7 @@ export default function UserResetPassword({ onBlur={() => setIsPasswordFieldFocused(false)} /> - {isPasswordFieldFocused && ( + {isPasswordFieldFocused ? (
+ ) : ( + )} )} From 5a7c4e25ec30b61b11d6a45b37ef766e68349fe9 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Sat, 25 Jan 2025 00:22:59 +0530 Subject: [PATCH 23/28] Empty-Commit From 82f25cbd3726f654c870498cd74e9443ce4da1f4 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Tue, 28 Jan 2025 15:01:45 +0530 Subject: [PATCH 24/28] Empty-Commit From 0ed55f91ec2b55adeac5c6f0309971e67cda7de6 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Tue, 28 Jan 2025 15:23:05 +0530 Subject: [PATCH 25/28] Empty-Commit From d51c8c7c963aa7f0c437e16c9634ee8bf96446dd Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Wed, 29 Jan 2025 23:58:53 +0530 Subject: [PATCH 26/28] add variant to button --- src/components/Users/UserForm.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/Users/UserForm.tsx b/src/components/Users/UserForm.tsx index 981fb6efee4..dae206f0b9d 100644 --- a/src/components/Users/UserForm.tsx +++ b/src/components/Users/UserForm.tsx @@ -701,6 +701,7 @@ export default function UserForm({ type="submit" className="w-full" data-cy="submit-user-form" + variant="primary_gradient" disabled={ isLoadingUser || !form.formState.isDirty || From 917fda4018e691cd84208b17b74932d8c8ae5554 Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Thu, 30 Jan 2025 00:14:01 +0530 Subject: [PATCH 27/28] Empty-Commit From 20182498fe70bfe9b6798fbe77dbe1029ac50abb Mon Sep 17 00:00:00 2001 From: AdityaJ2305 Date: Thu, 30 Jan 2025 00:27:24 +0530 Subject: [PATCH 28/28] add variant to button --- src/components/Users/UserForm.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Users/UserForm.tsx b/src/components/Users/UserForm.tsx index dae206f0b9d..d5656c89694 100644 --- a/src/components/Users/UserForm.tsx +++ b/src/components/Users/UserForm.tsx @@ -701,7 +701,7 @@ export default function UserForm({ type="submit" className="w-full" data-cy="submit-user-form" - variant="primary_gradient" + variant="primary" disabled={ isLoadingUser || !form.formState.isDirty ||