diff --git a/components/auth/signup.tsx b/components/auth/signup.tsx index 753539dbe..9190883ae 100644 --- a/components/auth/signup.tsx +++ b/components/auth/signup.tsx @@ -24,6 +24,8 @@ import { Label } from "@/components/ui/label"; import { Checkbox } from "@/components/ui/checkbox"; import { useToggle } from "@/hooks/useToggle"; import HCaptcha from "@hcaptcha/react-hcaptcha"; +import { PasswordStrength } from "@/components/ui/password-strength"; +import { FormSkeleton } from "@/components/ui/form-skeleton"; export default function SignUp() { const [isSubmitting, setIsSubmitting] = useState(false); @@ -40,6 +42,7 @@ export default function SignUp() { }, }); const router = useRouter(); + const [isValidating, setIsValidating] = useState(false); const handleSignUp = async (data: SignUpFormData) => { if (isSubmitting) return; @@ -97,6 +100,32 @@ export default function SignUp() { }; const [isPasswordVisible, togglePasswordVisibility] = useToggle(false); + const validateField = async (field: keyof SignUpFormData) => { + setIsValidating(true); + try { + await form.trigger(field); + } finally { + setIsValidating(false); + } + }; + + if (isSubmitting) { + return ( +
+
+
+
+

Creating your account...

+
+
+ +
+
+
+
+ ); + } + return (
@@ -169,6 +198,7 @@ export default function SignUp() { id="full_name" placeholder="First and last name" {...field} + onBlur={() => validateField("full_name")} /> @@ -187,6 +217,7 @@ export default function SignUp() { type="email" placeholder="helloworld@email.com" {...field} + onBlur={() => validateField("email")} /> @@ -206,8 +237,10 @@ export default function SignUp() { type={isPasswordVisible ? "text" : "password"} placeholder="Password (at least 8 characters)" {...field} + onBlur={() => validateField("password")} /> + )} diff --git a/components/ui/form-skeleton.tsx b/components/ui/form-skeleton.tsx new file mode 100644 index 000000000..8420568a9 --- /dev/null +++ b/components/ui/form-skeleton.tsx @@ -0,0 +1,20 @@ +import { cn } from "@/lib/utils"; + +interface FormSkeletonProps { + className?: string; + fieldCount?: number; +} + +export function FormSkeleton({ className, fieldCount = 4 }: FormSkeletonProps) { + return ( +
+ {Array.from({ length: fieldCount }).map((_, index) => ( +
+
+
+
+ ))} +
+
+ ); +} \ No newline at end of file diff --git a/components/ui/password-strength.tsx b/components/ui/password-strength.tsx new file mode 100644 index 000000000..a9b55f28b --- /dev/null +++ b/components/ui/password-strength.tsx @@ -0,0 +1,105 @@ +import { useEffect, useState } from "react"; +import { cn } from "@/lib/utils"; + +interface PasswordStrengthProps { + password: string; + className?: string; +} + +export function PasswordStrength({ password, className }: PasswordStrengthProps) { + const [strength, setStrength] = useState(0); + const [message, setMessage] = useState(""); + + useEffect(() => { + if (!password) { + setStrength(0); + setMessage(""); + return; + } + + let score = 0; + let feedback = []; + + // Length check + if (password.length >= 8) { + score += 1; + } else { + feedback.push("At least 8 characters"); + } + + // Uppercase check + if (/[A-Z]/.test(password)) { + score += 1; + } else { + feedback.push("One uppercase letter"); + } + + // Lowercase check + if (/[a-z]/.test(password)) { + score += 1; + } else { + feedback.push("One lowercase letter"); + } + + // Number check + if (/[0-9]/.test(password)) { + score += 1; + } else { + feedback.push("One number"); + } + + // Special character check + if (/[^A-Za-z0-9]/.test(password)) { + score += 1; + } else { + feedback.push("One special character"); + } + + setStrength(score); + + if (score === 0) { + setMessage(""); + } else if (score <= 2) { + setMessage("Weak - " + feedback.join(", ")); + } else if (score <= 3) { + setMessage("Medium - " + feedback.join(", ")); + } else if (score <= 4) { + setMessage("Strong - " + feedback.join(", ")); + } else { + setMessage("Very Strong"); + } + }, [password]); + + const getStrengthColor = () => { + if (strength <= 2) return "bg-red-500"; + if (strength <= 3) return "bg-yellow-500"; + if (strength <= 4) return "bg-blue-500"; + return "bg-green-500"; + }; + + return ( +
+
+
+
+ {message && ( +

+ {message} +

+ )} +
+ ); +} \ No newline at end of file