diff --git a/web/app/accounts/reset-password/page.tsx b/web/app/accounts/reset-password/page.tsx
index 14bcf27d6..43f001abe 100644
--- a/web/app/accounts/reset-password/page.tsx
+++ b/web/app/accounts/reset-password/page.tsx
@@ -20,7 +20,7 @@ import {
authErrorHandler,
} from "@/helpers/authentication.helper";
import { API_BASE_URL } from "@/helpers/common.helper";
-import { getPasswordStrength } from "@/helpers/password.helper";
+import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper";
// wrappers
import { AuthenticationWrapper } from "@/lib/wrappers";
// services
@@ -83,7 +83,7 @@ export default function ResetPasswordPage() {
const isButtonDisabled = useMemo(
() =>
!!resetFormData.password &&
- getPasswordStrength(resetFormData.password) >= 3 &&
+ getPasswordStrength(resetFormData.password) === E_PASSWORD_STRENGTH.STRENGTH_VALID &&
resetFormData.password === resetFormData.confirm_password
? false
: true,
@@ -187,7 +187,7 @@ export default function ResetPasswordPage() {
/>
)}
- {isPasswordInputFocused && }
+
- {isPasswordInputFocused && }
+
) : (
passwordFormData.password.length > 0 &&
- (getPasswordStrength(passwordFormData.password) < 3 || isPasswordInputFocused) && (
-
+ getPasswordStrength(passwordFormData.password) != E_PASSWORD_STRENGTH.STRENGTH_VALID && (
+
)
);
@@ -137,7 +137,7 @@ export const AuthPasswordForm: React.FC = observer((props: Props) => {
action={`${API_BASE_URL}/auth/${mode === EAuthModes.SIGN_IN ? "sign-in" : "sign-up"}/`}
onSubmit={(event) => {
event.preventDefault(); // Prevent form from submitting by default
- if (getPasswordStrength(passwordFormData.password) >= 3) {
+ if (getPasswordStrength(passwordFormData.password) === E_PASSWORD_STRENGTH.STRENGTH_VALID) {
setIsSubmitting(true);
captureEvent(mode === EAuthModes.SIGN_IN ? SIGN_IN_WITH_PASSWORD : SIGN_UP_WITH_PASSWORD);
event.currentTarget.submit(); // Manually submit the form if the condition is met
diff --git a/web/core/components/account/password-strength-meter.tsx b/web/core/components/account/password-strength-meter.tsx
index 7383b1e11..342f77efb 100644
--- a/web/core/components/account/password-strength-meter.tsx
+++ b/web/core/components/account/password-strength-meter.tsx
@@ -1,67 +1,94 @@
-// icons
-import { CircleCheck } from "lucide-react";
+"use client";
+
+import { FC, useMemo } from "react";
+// import { CircleCheck } from "lucide-react";
// helpers
import { cn } from "@/helpers/common.helper";
-import { getPasswordStrength } from "@/helpers/password.helper";
+import {
+ E_PASSWORD_STRENGTH,
+ // PASSWORD_CRITERIA,
+ getPasswordStrength,
+} from "@/helpers/password.helper";
-type Props = {
+type TPasswordStrengthMeter = {
password: string;
+ isFocused?: boolean;
};
-export const PasswordStrengthMeter: React.FC = (props: Props) => {
- const { password } = props;
+export const PasswordStrengthMeter: FC = (props) => {
+ const { password, isFocused = false } = props;
+ // derived values
+ const strength = useMemo(() => getPasswordStrength(password), [password]);
+ const strengthBars = useMemo(() => {
+ switch (strength) {
+ case E_PASSWORD_STRENGTH.EMPTY: {
+ return {
+ bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`],
+ text: "Please enter your password.",
+ textColor: "text-custom-text-100",
+ };
+ }
+ case E_PASSWORD_STRENGTH.LENGTH_NOT_VALID: {
+ return {
+ bars: [`bg-red-500`, `bg-custom-text-100`, `bg-custom-text-100`],
+ text: "Password length should me more than 8 characters.",
+ textColor: "text-red-500",
+ };
+ }
+ case E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID: {
+ return {
+ bars: [`bg-red-500`, `bg-custom-text-100`, `bg-custom-text-100`],
+ text: "Password is weak.",
+ textColor: "text-red-500",
+ };
+ }
+ case E_PASSWORD_STRENGTH.STRENGTH_VALID: {
+ return {
+ bars: [`bg-green-500`, `bg-green-500`, `bg-green-500`],
+ text: "Password is strong.",
+ textColor: "text-green-500",
+ };
+ }
+ default: {
+ return {
+ bars: [`bg-custom-text-100`, `bg-custom-text-100`, `bg-custom-text-100`],
+ text: "Please enter your password.",
+ textColor: "text-custom-text-100",
+ };
+ }
+ }
+ }, [strength]);
- const strength = getPasswordStrength(password);
- let bars = [];
- let text = "";
- let textColor = "";
-
- if (password.length === 0) {
- bars = [`bg-[#F0F0F3]`, `bg-[#F0F0F3]`, `bg-[#F0F0F3]`];
- text = "Password requirements";
- } else if (password.length < 8) {
- bars = [`bg-[#DC3E42]`, `bg-[#F0F0F3]`, `bg-[#F0F0F3]`];
- text = "Password is too short";
- textColor = `text-[#DC3E42]`;
- } else if (strength < 3) {
- bars = [`bg-[#DC3E42]`, `bg-[#F0F0F3]`, `bg-[#F0F0F3]`];
- text = "Password is weak";
- textColor = `text-[#DC3E42]`;
- } else {
- bars = [`bg-[#3E9B4F]`, `bg-[#3E9B4F]`, `bg-[#3E9B4F]`];
- text = "Password is strong";
- textColor = `text-[#3E9B4F]`;
- }
-
- const criteria = [
- { label: "Min 8 characters", isValid: password.length >= 8 },
- { label: "Min 1 upper-case letter", isValid: /[A-Z]/.test(password) },
- { label: "Min 1 number", isValid: /\d/.test(password) },
- { label: "Min 1 special character", isValid: /[!@#$%^&*]/.test(password) },
- ];
+ const isPasswordMeterVisible = isFocused ? true : strength === E_PASSWORD_STRENGTH.STRENGTH_VALID ? false : true;
+ if (!isPasswordMeterVisible) return <>>;
return (
-
-
- {bars.map((color, index) => (
-
- ))}
+
+
+
+ {strengthBars?.bars.map((color, index) => (
+
+ ))}
+
+
+ {strengthBars?.text}
+
-
{text}
-
- {criteria.map((criterion, index) => (
+
+ {/*
+ {PASSWORD_CRITERIA.map((criteria) => (
- {criterion.label}
+ {criteria.label}
))}
-
+
*/}
);
};
diff --git a/web/core/components/onboarding/profile-setup.tsx b/web/core/components/onboarding/profile-setup.tsx
index 722fbfa2f..26b6ceeb3 100644
--- a/web/core/components/onboarding/profile-setup.tsx
+++ b/web/core/components/onboarding/profile-setup.tsx
@@ -17,7 +17,7 @@ import { OnboardingHeader, SwitchAccountDropdown } from "@/components/onboarding
// constants
import { USER_DETAILS, E_ONBOARDING_STEP_1, E_ONBOARDING_STEP_2 } from "@/constants/event-tracker";
// helpers
-import { getPasswordStrength } from "@/helpers/password.helper";
+import { E_PASSWORD_STRENGTH, getPasswordStrength } from "@/helpers/password.helper";
// hooks
import { useEventTracker, useUser, useUserProfile } from "@/hooks/store";
// services
@@ -248,30 +248,30 @@ export const ProfileSetup: React.FC
= observer((props) => {
});
};
+ // derived values
const isPasswordAlreadySetup = !user?.is_password_autoset;
- const isSignUpUsingMagicCode = user?.last_login_medium === "magic-code";
-
- const password = watch("password");
- const confirmPassword = watch("confirm_password");
- const isValidPassword = (password: string, confirmPassword?: string) =>
- getPasswordStrength(password) >= 3 && password === confirmPassword;
+ const isValidPassword = useMemo(() => {
+ const currentPassword = watch("password") || undefined;
+ const currentConfirmPassword = watch("confirm_password") || undefined;
+ if (currentPassword) {
+ if (
+ currentPassword === currentConfirmPassword &&
+ getPasswordStrength(currentPassword) === E_PASSWORD_STRENGTH.STRENGTH_VALID
+ ) {
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }, [watch]);
// Check for all available fields validation and if password field is available, then checks for password validation (strength + confirmation).
// Also handles the condition for optional password i.e if password field is optional it only checks for above validation if it's not empty.
const isButtonDisabled = useMemo(
- () =>
- !isSubmitting &&
- isValid &&
- (isPasswordAlreadySetup
- ? true
- : isSignUpUsingMagicCode
- ? !!password && isValidPassword(password, confirmPassword)
- : !!password
- ? isValidPassword(password, confirmPassword)
- : true)
- ? false
- : true,
- [isSubmitting, isValid, isPasswordAlreadySetup, isSignUpUsingMagicCode, password, confirmPassword]
+ () => (!isSubmitting && isValid && (isPasswordAlreadySetup ? true : isValidPassword) ? false : true),
+ [isSubmitting, isValid, isPasswordAlreadySetup, isValidPassword]
);
const isCurrentStepUserPersonalization = profileSetupStep === EProfileSetupSteps.USER_PERSONALIZATION;
@@ -412,94 +412,98 @@ export const ProfileSetup: React.FC = observer((props) => {
{errors.last_name && {errors.last_name.message}}
+
+ {/* setting up password for the first time */}
{!isPasswordAlreadySetup && (
-
-
-
(
-
- )}
- {!isPasswordAlreadySetup && (
-
-
-
value === password || "Passwords don't match",
- }}
- render={({ field: { value, onChange, ref } }) => (
-
-
- {showPassword.retypePassword ? (
- handleShowPassword("retypePassword")}
- />
- ) : (
- handleShowPassword("retypePassword")}
- />
- )}
-
- )}
- />
- {errors.confirm_password && (
- {errors.confirm_password.message}
- )}
-
+
+ >
)}
>
)}
+
+ {/* user role once the password is set */}
{profileSetupStep !== EProfileSetupSteps.USER_DETAILS && (
<>
diff --git a/web/helpers/password.helper.ts b/web/helpers/password.helper.ts
index 8d80b3402..dfe9a5c65 100644
--- a/web/helpers/password.helper.ts
+++ b/web/helpers/password.helper.ts
@@ -1,16 +1,67 @@
import zxcvbn from "zxcvbn";
-export const isPasswordCriteriaMet = (password: string) => {
- const criteria = [password.length >= 8, /[A-Z]/.test(password), /\d/.test(password), /[!@#$%^&*]/.test(password)];
+export enum E_PASSWORD_STRENGTH {
+ EMPTY = "empty",
+ LENGTH_NOT_VALID = "length_not_valid",
+ STRENGTH_NOT_VALID = "strength_not_valid",
+ STRENGTH_VALID = "strength_valid",
+}
- return criteria.every((criterion) => criterion);
-};
-
-export const getPasswordStrength = (password: string) => {
- if (password.length === 0) return 0;
- if (password.length < 8) return 1;
- if (!isPasswordCriteriaMet(password)) return 2;
-
- const result = zxcvbn(password);
- return result.score;
+const PASSWORD_MIN_LENGTH = 8;
+// const PASSWORD_NUMBER_REGEX = /\d/;
+// const PASSWORD_CHAR_CAPS_REGEX = /[A-Z]/;
+// const PASSWORD_SPECIAL_CHAR_REGEX = /[`!@#$%^&*()_\-+=\[\]{};':"\\|,.<>\/?~ ]/;
+
+export const PASSWORD_CRITERIA = [
+ {
+ key: "min_8_char",
+ label: "Min 8 characters",
+ isCriteriaValid: (password: string) => password.length >= PASSWORD_MIN_LENGTH,
+ },
+ // {
+ // key: "min_1_upper_case",
+ // label: "Min 1 upper-case letter",
+ // isCriteriaValid: (password: string) => PASSWORD_NUMBER_REGEX.test(password),
+ // },
+ // {
+ // key: "min_1_number",
+ // label: "Min 1 number",
+ // isCriteriaValid: (password: string) => PASSWORD_CHAR_CAPS_REGEX.test(password),
+ // },
+ // {
+ // key: "min_1_special_char",
+ // label: "Min 1 special character",
+ // isCriteriaValid: (password: string) => PASSWORD_SPECIAL_CHAR_REGEX.test(password),
+ // },
+];
+
+export const getPasswordStrength = (password: string): E_PASSWORD_STRENGTH => {
+ let passwordStrength: E_PASSWORD_STRENGTH = E_PASSWORD_STRENGTH.EMPTY;
+
+ if (!password || password === "" || password.length <= 0) {
+ return passwordStrength;
+ }
+
+ if (password.length >= PASSWORD_MIN_LENGTH) {
+ passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID;
+ } else {
+ passwordStrength = E_PASSWORD_STRENGTH.LENGTH_NOT_VALID;
+ return passwordStrength;
+ }
+
+ const passwordCriteriaValidation = PASSWORD_CRITERIA.map((criteria) => criteria.isCriteriaValid(password)).every(
+ (criterion) => criterion
+ );
+ const passwordStrengthScore = zxcvbn(password).score;
+
+ if (passwordCriteriaValidation === false || passwordStrengthScore <= 2) {
+ passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID;
+ return passwordStrength;
+ }
+
+ if (passwordCriteriaValidation === true && passwordStrengthScore >= 3) {
+ passwordStrength = E_PASSWORD_STRENGTH.STRENGTH_VALID;
+ }
+
+ return passwordStrength;
};