password complexity indicator

This commit is contained in:
2025-02-10 22:55:54 +08:00
parent 65f3d8af1e
commit 2076455655
3 changed files with 71 additions and 2 deletions

View File

@@ -67,6 +67,7 @@ export default function LoginView({ onSignup }: { onSignup: () => void }) {
</div>
<div className="flex py-2 px-1 justify-between">
<Checkbox
isDisabled
classNames={{
label: "text-small",
}}

View File

@@ -0,0 +1,60 @@
import { Card } from "@heroui/react";
import React from "react";
interface PasswordComplexityIndicatorProps {
password: string;
}
const checkCriteria = (password: string) => ({
isLongEnough: password.length >= 12,
hasLowercase: /[a-z]/.test(password),
hasUppercase: /[A-Z]/.test(password),
hasNumber: /\d/.test(password),
hasSpecialChar: /[^A-Za-z\d]/.test(password),
});
// Dot indicator component
const DotIndicator = ({ met }: { met: boolean }) => (
<span
className={`w-3 h-3 rounded-full ${met ? "bg-green-500" : "bg-red-500"}`}
></span>
);
// List item component
const CriteriaItem = ({ label, met }: { label: string; met: boolean }) => (
<div className="flex items-center gap-2">
<DotIndicator met={met} />
<span className="text-sm opacity-50">{label}</span>
</div>
);
// Main password complexity component
const PasswordComplexityIndicator: React.FC<
PasswordComplexityIndicatorProps
> = ({ password }) => {
const criteria = checkCriteria(password);
const criteriaList = [
{ label: "12 characters", met: criteria.isLongEnough },
{ label: "Lowercase", met: criteria.hasLowercase },
{ label: "Uppercase", met: criteria.hasUppercase },
{ label: "Number", met: criteria.hasNumber },
{ label: "Special character", met: criteria.hasSpecialChar },
];
return (
<Card radius="sm" className="p-4 bg-neutral-500/10">
<div className="grid grid-cols-2">
{criteriaList.map((criterion, index) => (
<CriteriaItem
key={index}
label={criterion.label}
met={criterion.met}
/>
))}
</div>
</Card>
);
};
export default PasswordComplexityIndicator;

View File

@@ -3,6 +3,7 @@ import { IconMail, IconLock } from "@tabler/icons-react";
import { useState } from "react";
import { toast } from "react-toastify";
import http, { login } from "../http";
import PasswordComplexityIndicator from "./PasswordComplexityIndicator";
export const validatePassword = (password: string): boolean => {
const passwordComplexityRegex =
@@ -108,26 +109,30 @@ export default function SignupView({
<div className="flex flex-col gap-2">
<div className="flex flex-row gap-2">
<Input
size="sm"
label="First name"
value={firstName}
onValueChange={setFirstName}
/>
<Input
size="sm"
label="Last name"
value={lastName}
onValueChange={setLastName}
/>
</div>
<Input
size="sm"
endContent={<IconMail />}
label="Email"
type="email"
value={emailValue}
onValueChange={setEmailValue}
/>
<Input label="NRIC" value={nric} onValueChange={setNric} />
<Input size="sm" label="NRIC" value={nric} onValueChange={setNric} />
<div className="flex flex-row gap-2">
<Select
size="sm"
label="Gender"
selectedKeys={[gender]}
onChange={(e) => {
@@ -139,12 +144,13 @@ export default function SignupView({
</Select>
<input
type="date"
className="rounded-xl px-4 transition-colors dark:bg-neutral-800 dark:hover:bg-neutral-700"
className="rounded-lg px-4 transition-colors dark:bg-neutral-800 dark:hover:bg-neutral-700"
value={dateOfBirth}
onChange={(e) => setDateOfBirth(e.target.value)}
/>
</div>
<Input
size="sm"
endContent={<IconLock />}
label="Password"
type="password"
@@ -152,12 +158,14 @@ export default function SignupView({
onValueChange={setPassword}
/>
<Input
size="sm"
endContent={<IconLock />}
label="Confirm password"
type="password"
value={confirmPassword}
onValueChange={setConfirmPassword}
/>
<PasswordComplexityIndicator password={password} />
</div>
<div className="flex flex-col gap-4 w-full">
<Button