Merge pull request #79 from Chukwuebuka-2003/add-password-requirements-ui

Add visible password requirements with live validation checklist
This commit is contained in:
Ravi 2026-05-29 00:17:54 +05:30 committed by GitHub
commit 586a7e1d8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
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 FooterLink from '@/components/forms/FooterLink';
import InputField from '@/components/forms/InputField'; import InputField from '@/components/forms/InputField';
import PasswordRequirements from '@/components/forms/PasswordRequirements';
import OpenDevSocietyBranding from '@/components/OpenDevSocietyBranding'; import OpenDevSocietyBranding from '@/components/OpenDevSocietyBranding';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { resetPasswordWithToken } from '@/lib/actions/auth.actions'; import { resetPasswordWithToken } from '@/lib/actions/auth.actions';
import { PASSWORD_VALIDATION } from '@/lib/constants';
type ResetPasswordFormData = { type ResetPasswordFormData = {
newPassword: string; newPassword: string;
@ -86,8 +88,9 @@ const ResetPasswordForm = () => {
type="password" type="password"
register={register} register={register}
error={errors.newPassword} error={errors.newPassword}
validation={{ required: 'New password is required', minLength: 8 }} validation={PASSWORD_VALIDATION}
/> />
<PasswordRequirements password={newPassword ?? ''} />
<InputField <InputField
name="confirmPassword" name="confirmPassword"

View File

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