[WEB-4513] refactor: consolidate password strength meter into shared ui package (#7462)
* refactor: consolidate password strength indicator into shared UI package * chore: remove old password strength meter implementations * chore: update package dependencies for password strength refactor * chore: code refactor * fix: lock file --------- Co-authored-by: sriramveeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
parent
63d025cbf4
commit
a5f3bd15b1
25 changed files with 310 additions and 458 deletions
|
|
@ -2,3 +2,4 @@ export * from "./input";
|
|||
export * from "./textarea";
|
||||
export * from "./input-color-picker";
|
||||
export * from "./checkbox";
|
||||
export * from "./password";
|
||||
|
|
|
|||
65
packages/ui/src/form-fields/password/helper.tsx
Normal file
65
packages/ui/src/form-fields/password/helper.tsx
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { E_PASSWORD_STRENGTH } from "@plane/constants";
|
||||
|
||||
export interface StrengthInfo {
|
||||
message: string;
|
||||
textColor: string;
|
||||
activeFragments: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get strength information including message, color, and active fragments
|
||||
*/
|
||||
export const getStrengthInfo = (strength: E_PASSWORD_STRENGTH): StrengthInfo => {
|
||||
switch (strength) {
|
||||
case E_PASSWORD_STRENGTH.EMPTY:
|
||||
return {
|
||||
message: "Please enter your password",
|
||||
textColor: "text-custom-text-100",
|
||||
activeFragments: 0,
|
||||
};
|
||||
case E_PASSWORD_STRENGTH.LENGTH_NOT_VALID:
|
||||
return {
|
||||
message: "Password is too short",
|
||||
textColor: "text-red-500",
|
||||
activeFragments: 1,
|
||||
};
|
||||
case E_PASSWORD_STRENGTH.STRENGTH_NOT_VALID:
|
||||
return {
|
||||
message: "Password is weak",
|
||||
textColor: "text-orange-500",
|
||||
activeFragments: 2,
|
||||
};
|
||||
case E_PASSWORD_STRENGTH.STRENGTH_VALID:
|
||||
return {
|
||||
message: "Password is strong",
|
||||
textColor: "text-green-500",
|
||||
activeFragments: 3,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
message: "Please enter your password",
|
||||
textColor: "text-custom-text-100",
|
||||
activeFragments: 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get fragment color based on position and active state
|
||||
*/
|
||||
export const getFragmentColor = (fragmentIndex: number, activeFragments: number): string => {
|
||||
if (fragmentIndex >= activeFragments) {
|
||||
return "bg-custom-background-90";
|
||||
}
|
||||
|
||||
switch (activeFragments) {
|
||||
case 1:
|
||||
return "bg-red-500";
|
||||
case 2:
|
||||
return "bg-orange-500";
|
||||
case 3:
|
||||
return "bg-green-500";
|
||||
default:
|
||||
return "bg-custom-background-90";
|
||||
}
|
||||
};
|
||||
2
packages/ui/src/form-fields/password/index.ts
Normal file
2
packages/ui/src/form-fields/password/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./indicator";
|
||||
export * from "./helper";
|
||||
75
packages/ui/src/form-fields/password/indicator.tsx
Normal file
75
packages/ui/src/form-fields/password/indicator.tsx
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { CircleCheck } from "lucide-react";
|
||||
import React from "react";
|
||||
import { E_PASSWORD_STRENGTH } from "@plane/constants";
|
||||
import { cn, getPasswordStrength, getPasswordCriteria } from "@plane/utils";
|
||||
import { getStrengthInfo, getFragmentColor } from "./helper";
|
||||
|
||||
export interface PasswordStrengthIndicatorProps {
|
||||
password: string;
|
||||
showCriteria?: boolean;
|
||||
isFocused?: boolean;
|
||||
}
|
||||
|
||||
export const PasswordStrengthIndicator: React.FC<PasswordStrengthIndicatorProps> = ({
|
||||
password,
|
||||
showCriteria = true,
|
||||
isFocused = false,
|
||||
}) => {
|
||||
const strength = getPasswordStrength(password);
|
||||
const criteria = getPasswordCriteria(password);
|
||||
const strengthInfo = getStrengthInfo(strength);
|
||||
|
||||
const isPasswordMeterVisible = isFocused ? true : strength === E_PASSWORD_STRENGTH.STRENGTH_VALID ? false : true;
|
||||
|
||||
if ((!password && !showCriteria) || !isPasswordMeterVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn("space-y-3")}>
|
||||
{/* Strength Indicator */}
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-1 w-full transition-all duration-300 ease-linear">
|
||||
{[0, 1, 2].map((fragmentIndex) => (
|
||||
<div
|
||||
key={fragmentIndex}
|
||||
className={cn(
|
||||
"h-1 flex-1 rounded-sm transition-all duration-300 ease-in-out",
|
||||
getFragmentColor(fragmentIndex, strengthInfo.activeFragments)
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Strength Message */}
|
||||
{password && <p className={cn("text-sm font-medium", strengthInfo.textColor)}>{strengthInfo.message}</p>}
|
||||
</div>
|
||||
|
||||
{/* Criteria list */}
|
||||
{showCriteria && (
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{criteria.map((criterion) => (
|
||||
<div key={criterion.key} className="flex items-center gap-1.5">
|
||||
<div className="flex items-center justify-center p-0.5">
|
||||
<CircleCheck
|
||||
className={cn("h-3 w-3 flex-shrink-0", {
|
||||
"text-green-500": criterion.isValid,
|
||||
"text-custom-text-100": !criterion.isValid,
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
<span
|
||||
className={cn("text-xs", {
|
||||
"text-green-500": criterion.isValid,
|
||||
"text-custom-text-100": !criterion.isValid,
|
||||
})}
|
||||
>
|
||||
{criterion.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue