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>
<div className="flex py-2 px-1 justify-between"> <div className="flex py-2 px-1 justify-between">
<Checkbox <Checkbox
isDisabled
classNames={{ classNames={{
label: "text-small", 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 { useState } from "react";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import http, { login } from "../http"; import http, { login } from "../http";
import PasswordComplexityIndicator from "./PasswordComplexityIndicator";
export const validatePassword = (password: string): boolean => { export const validatePassword = (password: string): boolean => {
const passwordComplexityRegex = const passwordComplexityRegex =
@@ -108,26 +109,30 @@ export default function SignupView({
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex flex-row gap-2"> <div className="flex flex-row gap-2">
<Input <Input
size="sm"
label="First name" label="First name"
value={firstName} value={firstName}
onValueChange={setFirstName} onValueChange={setFirstName}
/> />
<Input <Input
size="sm"
label="Last name" label="Last name"
value={lastName} value={lastName}
onValueChange={setLastName} onValueChange={setLastName}
/> />
</div> </div>
<Input <Input
size="sm"
endContent={<IconMail />} endContent={<IconMail />}
label="Email" label="Email"
type="email" type="email"
value={emailValue} value={emailValue}
onValueChange={setEmailValue} 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"> <div className="flex flex-row gap-2">
<Select <Select
size="sm"
label="Gender" label="Gender"
selectedKeys={[gender]} selectedKeys={[gender]}
onChange={(e) => { onChange={(e) => {
@@ -139,12 +144,13 @@ export default function SignupView({
</Select> </Select>
<input <input
type="date" 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} value={dateOfBirth}
onChange={(e) => setDateOfBirth(e.target.value)} onChange={(e) => setDateOfBirth(e.target.value)}
/> />
</div> </div>
<Input <Input
size="sm"
endContent={<IconLock />} endContent={<IconLock />}
label="Password" label="Password"
type="password" type="password"
@@ -152,12 +158,14 @@ export default function SignupView({
onValueChange={setPassword} onValueChange={setPassword}
/> />
<Input <Input
size="sm"
endContent={<IconLock />} endContent={<IconLock />}
label="Confirm password" label="Confirm password"
type="password" type="password"
value={confirmPassword} value={confirmPassword}
onValueChange={setConfirmPassword} onValueChange={setConfirmPassword}
/> />
<PasswordComplexityIndicator password={password} />
</div> </div>
<div className="flex flex-col gap-4 w-full"> <div className="flex flex-col gap-4 w-full">
<Button <Button