diff --git a/Frontend/src/pages/BasicDetails.tsx b/Frontend/src/pages/BasicDetails.tsx index d72e0ef..48ff131 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,157 @@ import { UserNav } from "../components/user-nav"; import { Link } from "react-router-dom"; import { ModeToggle } from "../components/mode-toggle"; +// Validation utilities +const validatePhone = (phone: string): boolean => { + // 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 validateEmail = (email: string): boolean => { + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return emailRegex.test(email.trim()); +}; + + +const validateURL = (url: string): boolean => { + try { + const parsed = new URL(url.trim()); + return parsed.protocol === "http:" || parsed.protocol === "https:"; + } 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 +214,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 +293,11 @@ export default function BasicDetails() { Education + {errors.category && ( +

+ {errors.category} +

+ )}
); @@ -131,35 +309,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 +385,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 +430,16 @@ export default function BasicDetails() { Twitter + {errors.mainPlatform && ( +

+ {errors.mainPlatform} +

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

+ {errors.audienceAge} +

+ )}
); @@ -223,18 +465,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 +511,16 @@ export default function BasicDetails() { Entertainment + {errors.industry && ( +

+ {errors.industry} +

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

+ {errors.size} +

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

+ {errors.budget} +

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

Campaign Settings

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

+ {errors.targetAudience} +

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

+ {errors.preferredPlatforms} +

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

+ {errors.campaignGoals} +

+ )}
); @@ -543,12 +838,8 @@ export default function BasicDetails() {