Add visible password requirements with live validation checklist

- Add PasswordRequirements component showing rules with live checkmarks
- Add PASSWORD_RULES and PASSWORD_VALIDATION constants
- Update sign-up and reset-password forms to show requirements
This commit is contained in:
Chukuwebuka-2003 2026-05-26 14:01:37 +01:00
parent e06802738c
commit f2d72d8825
4 changed files with 65 additions and 3 deletions

View File

@ -7,9 +7,11 @@ import { toast } from 'sonner';
import FooterLink from '@/components/forms/FooterLink';
import InputField from '@/components/forms/InputField';
import PasswordRequirements from '@/components/forms/PasswordRequirements';
import OpenDevSocietyBranding from '@/components/OpenDevSocietyBranding';
import { Button } from '@/components/ui/button';
import { resetPasswordWithToken } from '@/lib/actions/auth.actions';
import { PASSWORD_VALIDATION } from '@/lib/constants';
type ResetPasswordFormData = {
newPassword: string;
@ -86,8 +88,9 @@ const ResetPasswordForm = () => {
type="password"
register={register}
error={errors.newPassword}
validation={{ required: 'New password is required', minLength: 8 }}
validation={PASSWORD_VALIDATION}
/>
<PasswordRequirements password={newPassword ?? ''} />
<InputField
name="confirmPassword"

View File

@ -4,7 +4,8 @@ import { useForm } from "react-hook-form";
import { Button } from "@/components/ui/button";
import InputField from "@/components/forms/InputField";
import SelectField from "@/components/forms/SelectField";
import { INVESTMENT_GOALS, PREFERRED_INDUSTRIES, RISK_TOLERANCE_OPTIONS } from "@/lib/constants";
import PasswordRequirements from "@/components/forms/PasswordRequirements";
import { INVESTMENT_GOALS, PASSWORD_VALIDATION, PREFERRED_INDUSTRIES, RISK_TOLERANCE_OPTIONS } from "@/lib/constants";
import { CountrySelectField } from "@/components/forms/CountrySelectField";
import FooterLink from "@/components/forms/FooterLink";
import { signUpWithEmail } from "@/lib/actions/auth.actions";
@ -19,6 +20,7 @@ const SignUp = () => {
register,
handleSubmit,
control,
watch,
formState: { errors, isSubmitting },
} = useForm<SignUpFormData>({
defaultValues: {
@ -33,6 +35,8 @@ const SignUp = () => {
mode: 'onBlur'
},);
const passwordValue = watch('password');
const onSubmit = async (data: SignUpFormData) => {
try {
const result = await signUpWithEmail(data);
@ -87,8 +91,9 @@ const SignUp = () => {
type="password"
register={register}
error={errors.password}
validation={{ required: 'Password is required', minLength: 8 }}
validation={PASSWORD_VALIDATION}
/>
<PasswordRequirements password={passwordValue ?? ''} />
<CountrySelectField
name="country"

View File

@ -0,0 +1,38 @@
'use client';
import React from 'react';
import { PASSWORD_RULES } from '@/lib/constants';
import { cn } from '@/lib/utils';
import { Check, X } from 'lucide-react';
const PasswordRequirements = ({ password }: { password: string }) => {
return (
<ul className="space-y-1.5 mt-2">
{PASSWORD_RULES.map((rule) => {
const passed = rule.test(password);
return (
<li key={rule.label} className="flex items-center gap-2 text-xs">
{password.length === 0 ? (
<span className="size-3.5 rounded-full border border-gray-500" />
) : passed ? (
<Check className="size-3.5 text-green-500" />
) : (
<X className="size-3.5 text-red-500" />
)}
<span
className={cn(
'transition-colors',
password.length === 0 && 'text-gray-500',
passed ? 'text-green-500' : password.length > 0 && 'text-red-500',
)}
>
{rule.label}
</span>
</li>
);
})}
</ul>
);
};
export default PasswordRequirements;

View File

@ -338,3 +338,19 @@ export const WATCHLIST_TABLE_HEADER = [
'Alert',
'Action',
];
export const PASSWORD_RULES = [
{ label: 'At least 8 characters', test: (pw: string) => pw.length >= 8 },
{ label: 'At least 1 uppercase letter', test: (pw: string) => /[A-Z]/.test(pw) },
{ label: 'At least 1 lowercase letter', test: (pw: string) => /[a-z]/.test(pw) },
{ label: 'At least 1 number', test: (pw: string) => /[0-9]/.test(pw) },
] as const;
export const PASSWORD_VALIDATION = {
required: 'Password is required',
minLength: { value: 8, message: 'Password must be at least 8 characters' },
pattern: {
value: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
message: 'Password must include uppercase, lowercase, and a number',
},
};