From 14b8212ccd1cbbe39bf5754df797ebd3fce05607 Mon Sep 17 00:00:00 2001 From: Divyanshu singh Date: Fri, 21 Nov 2025 03:07:55 +0530 Subject: [PATCH 1/3] fix: Add comprehensive input validation to BasicDetails form --- Frontend/src/pages/BasicDetails.tsx | 389 ++++++++++++++++++++++++---- 1 file changed, 341 insertions(+), 48 deletions(-) diff --git a/Frontend/src/pages/BasicDetails.tsx b/Frontend/src/pages/BasicDetails.tsx index d72e0ef..70048ae 100644 --- a/Frontend/src/pages/BasicDetails.tsx +++ b/Frontend/src/pages/BasicDetails.tsx @@ -25,6 +25,7 @@ import { ChevronLeft, Rocket, Check, + AlertCircle, } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; import { useState, useEffect } from "react"; @@ -33,13 +34,155 @@ import { UserNav } from "../components/user-nav"; import { Link } from "react-router-dom"; import { ModeToggle } from "../components/mode-toggle"; +// Validation utilities +const validateEmail = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email); +}; + +const validatePhone = (phone: string): boolean => { + const phoneRegex = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/; + return phoneRegex.test(phone); +}; + +const validateURL = (url: string): boolean => { + try { + new URL(url); + return true; + } catch { + return false; + } +}; + export default function BasicDetails() { const { user } = useParams(); const [step, setStep] = useState(0); const [animationDirection, setAnimationDirection] = useState(0); + + // Form state and validation + const [formData, setFormData] = useState({ + influencer: { + firstName: "", + lastName: "", + email: "", + phone: "", + category: "", + instagram: "", + youtube: "", + twitter: "", + tiktok: "", + website: "", + audienceSize: "", + avgEngagement: "", + mainPlatform: "", + audienceAge: "", + }, + brand: { + companyName: "", + website: "", + industry: "", + size: "", + budget: "", + targetAudience: "", + preferredPlatforms: "", + campaignGoals: "", + }, + }); + + const [errors, setErrors] = useState>({}); const totalSteps = user === "influencer" ? 3 : 2; + + // Validation functions + const validateStep = (): boolean => { + const newErrors: Record = {}; + const data = user === "influencer" ? formData.influencer : formData.brand; + + if (user === "influencer") { + const influData = data as typeof formData.influencer; + if (step === 0) { + if (!influData.firstName?.trim()) newErrors.firstName = "First name is required"; + if (!influData.lastName?.trim()) newErrors.lastName = "Last name is required"; + if (!influData.email?.trim()) { + newErrors.email = "Email is required"; + } else if (!validateEmail(influData.email)) { + newErrors.email = "Invalid email format"; + } + if (!influData.phone?.trim()) { + newErrors.phone = "Phone number is required"; + } else if (!validatePhone(influData.phone)) { + newErrors.phone = "Invalid phone format (e.g., +1-555-000-0000)"; + } + if (!influData.category) newErrors.category = "Content category is required"; + } else if (step === 1) { + if (influData.instagram && !influData.instagram.startsWith("@")) { + newErrors.instagram = "Instagram handle should start with @"; + } + if (influData.youtube && !validateURL(influData.youtube)) { + newErrors.youtube = "Invalid YouTube URL"; + } + if (influData.website && !validateURL(influData.website)) { + newErrors.website = "Invalid website URL"; + } + } else if (step === 2) { + if (!influData.audienceSize) newErrors.audienceSize = "Audience size is required"; + else if (isNaN(Number(influData.audienceSize)) || Number(influData.audienceSize) <= 0) { + newErrors.audienceSize = "Audience size must be a positive number"; + } + if (!influData.avgEngagement) newErrors.avgEngagement = "Engagement rate is required"; + else if (isNaN(Number(influData.avgEngagement)) || Number(influData.avgEngagement) < 0 || Number(influData.avgEngagement) > 100) { + newErrors.avgEngagement = "Engagement rate must be between 0 and 100"; + } + if (!influData.mainPlatform) newErrors.mainPlatform = "Primary platform is required"; + if (!influData.audienceAge) newErrors.audienceAge = "Audience age range is required"; + } + } else { + const brandData = data as typeof formData.brand; + if (step === 0) { + if (!brandData.companyName?.trim()) newErrors.companyName = "Company name is required"; + if (!brandData.website) { + newErrors.website = "Website is required"; + } else if (!validateURL(brandData.website)) { + newErrors.website = "Invalid website URL"; + } + if (!brandData.industry) newErrors.industry = "Industry is required"; + if (!brandData.size) newErrors.size = "Company size is required"; + if (!brandData.budget) newErrors.budget = "Budget range is required"; + } else if (step === 1) { + if (!brandData.targetAudience) newErrors.targetAudience = "Target audience is required"; + if (!brandData.preferredPlatforms) newErrors.preferredPlatforms = "Preferred platforms is required"; + if (!brandData.campaignGoals) newErrors.campaignGoals = "Campaign goals is required"; + } + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleInputChange = (field: string, value: string) => { + if (user === "influencer") { + setFormData(prev => ({ + ...prev, + influencer: { ...prev.influencer, [field]: value } + })); + } else { + setFormData(prev => ({ + ...prev, + brand: { ...prev.brand, [field]: value } + })); + } + // Clear error for this field + if (errors[field]) { + setErrors(prev => { + const newErrors = { ...prev }; + delete newErrors[field]; + return newErrors; + }); + } + }; const nextStep = () => { + if (!validateStep()) return; // Don't proceed if validation fails + if ((user === "influencer" && step < 2) || (user === "brand" && step < 1)) { setAnimationDirection(1); setTimeout(() => { @@ -69,44 +212,72 @@ export default function BasicDetails() {
- + handleInputChange("firstName", e.target.value)} + className={`border ${errors.firstName ? "border-red-500" : "border-gray-300"}`} /> + {errors.firstName && ( +

+ {errors.firstName} +

+ )}
- + handleInputChange("lastName", e.target.value)} + className={`border ${errors.lastName ? "border-red-500" : "border-gray-300"}`} /> + {errors.lastName && ( +

+ {errors.lastName} +

+ )}
- + handleInputChange("email", e.target.value)} + className={`border ${errors.email ? "border-red-500" : "border-gray-300"}`} /> + {errors.email && ( +

+ {errors.email} +

+ )}
- + handleInputChange("phone", e.target.value)} + className={`border ${errors.phone ? "border-red-500" : "border-gray-300"}`} /> + {errors.phone && ( +

+ {errors.phone} +

+ )}
- - handleInputChange("category", value)}> + @@ -120,6 +291,11 @@ export default function BasicDetails() { Education + {errors.category && ( +

+ {errors.category} +

+ )}
); @@ -131,35 +307,75 @@ export default function BasicDetails() { Instagram Handle - + handleInputChange("instagram", e.target.value)} + className={`border ${errors.instagram ? "border-red-500" : "border-gray-300"}`} + /> + {errors.instagram && ( +

+ {errors.instagram} +

+ )}
- + handleInputChange("youtube", e.target.value)} + className={`border ${errors.youtube ? "border-red-500" : "border-gray-300"}`} + /> + {errors.youtube && ( +

+ {errors.youtube} +

+ )}
- + handleInputChange("twitter", e.target.value)} + className="border border-gray-300" + />
- + handleInputChange("tiktok", e.target.value)} + className="border border-gray-300" + />
- + handleInputChange("website", e.target.value)} + className={`border ${errors.website ? "border-red-500" : "border-gray-300"}`} + /> + {errors.website && ( +

+ {errors.website} +

+ )}
); @@ -167,28 +383,42 @@ export default function BasicDetails() { const InfluencerAudience = () => (
- + handleInputChange("audienceSize", e.target.value)} + className={`border ${errors.audienceSize ? "border-red-500" : "border-gray-300"}`} /> + {errors.audienceSize && ( +

+ {errors.audienceSize} +

+ )}
- + handleInputChange("avgEngagement", e.target.value)} + className={`border ${errors.avgEngagement ? "border-red-500" : "border-gray-300"}`} /> + {errors.avgEngagement && ( +

+ {errors.avgEngagement} +

+ )}
- - handleInputChange("mainPlatform", value)}> + @@ -198,11 +428,16 @@ export default function BasicDetails() { Twitter + {errors.mainPlatform && ( +

+ {errors.mainPlatform} +

+ )}
- - handleInputChange("audienceAge", value)}> + @@ -213,6 +448,11 @@ export default function BasicDetails() { 45+ + {errors.audienceAge && ( +

+ {errors.audienceAge} +

+ )}
); @@ -223,18 +463,41 @@ export default function BasicDetails() {

Brand Information

- - + + handleInputChange("companyName", e.target.value)} + className={`border ${errors.companyName ? "border-red-500" : "border-gray-300"}`} + /> + {errors.companyName && ( +

+ {errors.companyName} +

+ )}
- - + + handleInputChange("website", e.target.value)} + className={`border ${errors.website ? "border-red-500" : "border-gray-300"}`} + /> + {errors.website && ( +

+ {errors.website} +

+ )}
- - handleInputChange("industry", value)}> + @@ -246,11 +509,16 @@ export default function BasicDetails() { Entertainment + {errors.industry && ( +

+ {errors.industry} +

+ )}
- - handleInputChange("size", value)}> + @@ -261,12 +529,17 @@ export default function BasicDetails() { 501+ employees + {errors.size && ( +

+ {errors.size} +

+ )}
- - handleInputChange("budget", value)}> + @@ -276,6 +549,11 @@ export default function BasicDetails() { $50,001+ + {errors.budget && ( +

+ {errors.budget} +

+ )}
); @@ -286,9 +564,9 @@ export default function BasicDetails() {

Campaign Settings

- - handleInputChange("targetAudience", value)}> + @@ -299,11 +577,16 @@ export default function BasicDetails() { 45+ + {errors.targetAudience && ( +

+ {errors.targetAudience} +

+ )}
- - handleInputChange("preferredPlatforms", value)}> + @@ -313,11 +596,16 @@ export default function BasicDetails() { Twitter + {errors.preferredPlatforms && ( +

+ {errors.preferredPlatforms} +

+ )}
- - handleInputChange("campaignGoals", value)}> + @@ -327,6 +615,11 @@ export default function BasicDetails() { Brand Loyalty + {errors.campaignGoals && ( +

+ {errors.campaignGoals} +

+ )}
); From 849e47ffb2f0886a8728229fb227039807e6f3fb Mon Sep 17 00:00:00 2001 From: Divyanshu singh Date: Fri, 21 Nov 2025 03:29:35 +0530 Subject: [PATCH 2/3] fixed the issue --- Frontend/src/pages/BasicDetails.tsx | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/Frontend/src/pages/BasicDetails.tsx b/Frontend/src/pages/BasicDetails.tsx index 70048ae..5f6f0a1 100644 --- a/Frontend/src/pages/BasicDetails.tsx +++ b/Frontend/src/pages/BasicDetails.tsx @@ -35,20 +35,17 @@ import { Link } from "react-router-dom"; import { ModeToggle } from "../components/mode-toggle"; // Validation utilities -const validateEmail = (email: string): boolean => { - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - return emailRegex.test(email); -}; - const validatePhone = (phone: string): boolean => { - const phoneRegex = /^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/; - return phoneRegex.test(phone); + // Basic pattern: optional country code + local number with common separators + const phoneRegex = + /^\+?[0-9]{1,3}?[-\s.]?\(?[0-9]{3}\)?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4}$/; + return phoneRegex.test(phone.trim()); }; const validateURL = (url: string): boolean => { try { - new URL(url); - return true; + const parsed = new URL(url.trim()); + return parsed.protocol === "http:" || parsed.protocol === "https:"; } catch { return false; } @@ -836,12 +833,8 @@ export default function BasicDetails() { From 6302d6dc53a6c770c08aa3a27a88e7cb0dd6ccbd Mon Sep 17 00:00:00 2001 From: Divyanshu singh Date: Fri, 21 Nov 2025 03:35:38 +0530 Subject: [PATCH 3/3] issue solved --- Frontend/src/pages/BasicDetails.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Frontend/src/pages/BasicDetails.tsx b/Frontend/src/pages/BasicDetails.tsx index 5f6f0a1..48ff131 100644 --- a/Frontend/src/pages/BasicDetails.tsx +++ b/Frontend/src/pages/BasicDetails.tsx @@ -41,6 +41,11 @@ const validatePhone = (phone: string): boolean => { /^\+?[0-9]{1,3}?[-\s.]?\(?[0-9]{3}\)?[-\s.]?[0-9]{3}[-\s.]?[0-9]{4}$/; return phoneRegex.test(phone.trim()); }; +const validateEmail = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email.trim()); +}; + const validateURL = (url: string): boolean => { try {