Files
Wdipl-react/components/AboutYourProject.tsx

740 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useRef } from "react";
import { motion } from "framer-motion";
import { Button } from "../components/ui/button";
import { ShimmerButton } from "../components/ui/shimmer-button";
import { Card, CardContent } from "../components/ui/card";
import { Input } from "../components/ui/input";
import { Textarea } from "../components/ui/textarea";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../components/ui/select";
import { Checkbox } from "../components/ui/checkbox";
import { RadioGroup, RadioGroupItem } from "../components/ui/radio-group";
import { Label } from "../components/ui/label";
import { MathVerificationPopup } from "../components/MathVerificationPopup";
import { useStoreContactUsMutation } from "@/src/services/storeContactUs";
import { useNavigate } from "react-router-dom";
import * as Yup from "yup";
import {
Rocket,
Upload,
FileText,
CheckCircle,
ArrowRight,
Shield,
} from "lucide-react";
interface AboutYourProjectProps {
// You can add any props you might need to pass from the parent component
}
// Country codes constant
const COUNTRY_CODES: Record<string, string> = {
us: "+1",
uk: "+44",
ca: "+1",
au: "+61",
in: "+91",
de: "+49",
fr: "+33",
other: "+",
};
export const AboutYourProject: React.FC<AboutYourProjectProps> = () => {
const [isHumanVerified, setIsHumanVerified] = useState(false);
const [showVerificationPopup, setShowVerificationPopup] = useState(false);
const [formData, setFormData] = useState({
name: "",
email: "",
country: "",
phone: "",
services: "",
budget: "",
projectDescription: "",
developmentStage: "",
timeline: "",
ndaRequired: false,
agreeTerms: false,
});
const [errors, setErrors] = useState<Record<string, string>>({});
const [touched, setTouched] = useState<Record<string, boolean>>({});
const [attachedFiles, setAttachedFiles] = useState<File[]>([]);
const [submitContactForm, { isLoading }] = useStoreContactUsMutation();
const navigate = useNavigate();
// Updated Validation Schema (removed recaptcha)
const validationSchema = Yup.object().shape({
name: Yup.string()
.required("Name is required")
.min(2, "Name must be at least 2 characters")
.max(50, "Name must not exceed 50 characters"),
email: Yup.string()
.required("Email is required")
.email("Invalid email address"),
country: Yup.string().required("Country is required"),
phone: Yup.string()
.required("Phone number is required")
.matches(/^[\d\s+\-().]{10,}$/, "Please enter a valid phone number"),
services: Yup.string().required("Service selection is required"),
budget: Yup.string().required("Budget range is required"),
projectDescription: Yup.string()
.required("Project description is required")
.min(50, "Description should be at least 50 characters")
.max(2000, "Description must not exceed 2000 characters"),
developmentStage: Yup.string().required("Development stage is required"),
timeline: Yup.string().required("Timeline is required"),
ndaRequired: Yup.boolean(),
agreeTerms: Yup.boolean()
.oneOf([true], "You must agree to the terms and conditions")
.required("You must agree to the terms and conditions"),
}).test(
'human-verification',
'Human verification is required',
function (value) {
return isHumanVerified;
}
);
const handleBlur = (field: string) => {
setTouched({ ...touched, [field]: true });
validateField(field);
};
const validateField = async (field: string, dataToValidate = formData) => {
try {
await validationSchema.validateAt(field, dataToValidate);
setErrors((prev) => ({ ...prev, [field]: "" }));
} catch (err) {
if (err instanceof Yup.ValidationError) {
setErrors((prev) => ({ ...prev, [field]: err.message }));
}
}
};
const validateForm = async () => {
try {
await validationSchema.validate(formData, { abortEarly: false });
setErrors({});
return true;
} catch (err) {
if (err instanceof Yup.ValidationError) {
const newErrors: Record<string, string> = {};
err.inner.forEach((error) => {
if (error.path) {
newErrors[error.path] = error.message;
}
});
setErrors(newErrors);
// Scroll to first error
const firstError = err.inner[0];
if (firstError?.path) {
const element = document.getElementById(firstError.path);
if (element) {
element.scrollIntoView({ behavior: "smooth", block: "center" });
}
}
}
return false;
}
};
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const files = event.target.files;
if (files) {
const validFiles = Array.from(files).filter(
(file) => file.size <= 10 * 1024 * 1024
);
setAttachedFiles((prev) => [...prev, ...validFiles]);
}
};
const removeFile = (index: number) => {
setAttachedFiles((prev) => prev.filter((_, i) => i !== index));
};
const handleHumanVerification = () => {
setIsHumanVerified(true);
setShowVerificationPopup(false);
};
const handleVerificationCheckbox = (checked: boolean) => {
if (checked) {
setShowVerificationPopup(true);
} else {
setIsHumanVerified(false);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!isHumanVerified) {
setErrors(prev => ({
...prev,
humanVerification: "Please complete the human verification"
}));
setShowVerificationPopup(true);
return;
}
const isValid = await validateForm();
if (!isValid) return;
try {
const formDataToSend = new FormData();
formDataToSend.append("t_id", "xyz123");
formDataToSend.append("name", formData.name);
formDataToSend.append("email", formData.email);
formDataToSend.append("country", formData.country);
formDataToSend.append("phone_number", formData.phone);
formDataToSend.append("service", formData.services);
formDataToSend.append("budget", formData.budget);
formDataToSend.append("message", formData.projectDescription);
formDataToSend.append("development_stage", formData.developmentStage);
formDataToSend.append("startTime", formData.timeline);
formDataToSend.append("nda_signing", formData.ndaRequired ? "1" : "0");
formDataToSend.append("from_page", "contact-us");
if (attachedFiles.length > 0) {
attachedFiles.forEach((file) => {
formDataToSend.append("contact_us_attachment", file);
});
}
formDataToSend.append("ip", "192.168.1.10");
formDataToSend.append("user_agent", navigator.userAgent);
await submitContactForm(formDataToSend).unwrap();
// Reset form
setFormData({
name: "",
email: "",
country: "",
phone: "",
services: "",
budget: "",
projectDescription: "",
developmentStage: "",
timeline: "",
ndaRequired: false,
agreeTerms: false,
});
setAttachedFiles([]);
setIsHumanVerified(false);
navigate("/thank-you");
} catch (error) {
console.error("Form submission error:", error);
alert("Failed to submit the form. Please try again.");
}
};
// Helper components for form fields (keep the same as before)
const renderInputField = (
field: keyof typeof formData,
label: string,
placeholder: string,
type = "text"
) => (
<div className="space-y-3" id={field}>
<label className="block text-sm font-medium text-white">{label} *</label>
<Input
type={type}
placeholder={placeholder}
className={`bg-gray-800/30 border-gray-600/50 text-white h-12 text-base ${errors[field] ? "border-red-500" : ""
}`}
value={formData[field] as string}
onChange={(e) => setFormData({ ...formData, [field]: e.target.value })}
onBlur={() => handleBlur(field)}
/>
{errors[field] && (
<p className="text-red-400 text-sm mt-1">{errors[field]}</p>
)}
</div>
);
const renderSelectField = (
field: keyof typeof formData,
label: string,
placeholder: string,
options: { value: string; label: string }[],
onValueChange?: (value: string) => void
) => (
<div className="space-y-3" id={field}>
<label className="block text-sm font-medium text-white">{label} *</label>
<Select
value={formData[field] as string}
onValueChange={(value) => {
const updated = { ...formData, [field]: value };
setFormData(updated);
setTouched({ ...touched, [field]: true });
validateField(field, updated);
if (onValueChange) {
onValueChange(value);
}
}}
>
<SelectTrigger
className={`bg-gray-800/30 border-gray-600/50 text-white h-12 min-h-12 ${errors[field] ? "border-red-500" : ""
}`}
>
<SelectValue placeholder={placeholder} />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
{errors[field] && (
<p className="text-red-400 text-sm mt-1">{errors[field]}</p>
)}
</div>
);
const renderTextarea = (
field: keyof typeof formData,
label: string,
placeholder: string,
rows = 6
) => (
<div className="space-y-3" id={field}>
<div className="flex justify-between">
<label className="block text-sm font-medium text-white">
{label} *
</label>
<span className="text-sm text-gray-400">
{formData[field]?.toString().length || 0}/2000
</span>
</div>
<Textarea
placeholder={placeholder}
rows={rows}
className={`bg-gray-800/30 border-gray-600/50 text-white text-base resize-none ${errors[field] ? "border-red-500" : ""
}`}
value={formData[field] as string}
onChange={(e) => setFormData({ ...formData, [field]: e.target.value })}
onBlur={() => handleBlur(field)}
/>
{errors[field] && (
<p className="text-red-400 text-sm mt-1">{errors[field]}</p>
)}
</div>
);
const renderRadioGroup = (
field: keyof typeof formData,
label: string,
options: { value: string; label: string }[],
cols: string
) => (
<div className="space-y-4" id={field}>
<label className="block text-sm font-medium text-white">{label} *</label>
{errors[field] && (
<p className="text-red-400 text-sm mb-2">{errors[field]}</p>
)}
<RadioGroup
value={formData[field] as string}
onValueChange={(value) => {
const updated = { ...formData, [field]: value };
setFormData(updated);
setTouched({ ...touched, [field]: true });
validateField(field, updated);
}}
className={`grid ${cols} gap-4`}
>
{options.map((option) => (
<div
key={option.value}
className="flex items-center space-x-3 p-4 bg-gray-800/20 rounded-lg border border-gray-700/30"
>
<RadioGroupItem
value={option.value}
id={`${field}-${option.value}`}
className="border-gray-600"
/>
<Label
htmlFor={`${field}-${option.value}`}
className="text-white cursor-pointer"
>
{option.label}
</Label>
</div>
))}
</RadioGroup>
</div>
);
const renderCheckbox = (
field: keyof typeof formData,
label: React.ReactNode,
required = false
) => (
<div className="space-y-2">
<div className="flex items-start space-x-4 p-4 bg-gray-800/20 rounded-lg border border-gray-700/30">
<Checkbox
id={field}
checked={formData[field] as boolean}
onCheckedChange={(checked) => {
const updated = { ...formData, [field]: checked };
setFormData(updated);
setTouched({ ...touched, [field]: true });
validateField(field, updated);
}}
className="mt-1"
/>
<label
htmlFor={field}
className="text-gray-300 leading-relaxed cursor-pointer"
>
{label}
</label>
</div>
{errors[field] && (
<p className="text-red-400 text-sm mt-1">{errors[field]}</p>
)}
</div>
);
return (
<section className="py-32 bg-wdi-grey">
<div className="container mx-auto px-6 lg:px-8">
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
viewport={{ once: true }}
className="text-center mb-16"
>
<h2 className="text-3xl lg:text-4xl font-semibold leading-tight mb-6">
<span className="text-white">Tell Us About Your </span>
<span className="text-[#E5195E]">Project</span>
</h2>
<p className="text-lg text-gray-300 leading-relaxed max-w-2xl mx-auto">
Fill out the form below and our AI mobile app and AI development experts will get back to you within 24 hours. </p>
<p className="text-lg text-gray-300 leading-relaxed max-w-2xl mx-auto">
Well review your idea, provide a tailored roadmap, and discuss the best approach. Whether its a custom AIpowered mobile app, AIdriven web application, or endtoend AI integration.
</p>
</motion.div>
<motion.div
initial={{ opacity: 0, y: 40 }}
whileInView={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.2 }}
viewport={{ once: true }}
className="max-w-4xl mx-auto"
>
<Card className="bg-gray-900/30 backdrop-blur-md border-gray-700/30 rounded-3xl overflow-hidden shadow-2xl">
<CardContent className="p-12">
<form onSubmit={handleSubmit} className="space-y-10">
{/* Personal Information Section */}
<div className="space-y-8">
<h3 className="text-xl font-semibold text-white border-b border-gray-700 pb-4">
Personal Information
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{renderInputField(
"name",
"Your Name",
"Enter your full name"
)}
{renderInputField(
"email",
"Email Address",
"your.email@company.com",
"email"
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{renderSelectField(
"country",
"Country",
"Select your country",
[
{ value: "us", label: "United States" },
{ value: "uk", label: "United Kingdom" },
{ value: "ca", label: "Canada" },
{ value: "au", label: "Australia" },
{ value: "in", label: "India" },
{ value: "de", label: "Germany" },
{ value: "fr", label: "France" },
{ value: "other", label: "Other" },
],
(value) => {
const updated = { ...formData, country: value };
if (!formData.phone || formData.phone.startsWith("+")) {
updated.phone = COUNTRY_CODES[value] || "";
}
setFormData(updated);
setTouched({ ...touched, country: true });
validateField("country", updated);
}
)}
{renderInputField(
"phone",
"Contact Number",
formData.country ? `${COUNTRY_CODES[formData.country] || "+"} (XXX) XXX-XXXX` : "+XX (XXX) XXX-XXXX"
)}
</div>
</div>
{/* Project Information Section */}
<div className="space-y-8">
<h3 className="text-xl font-semibold text-white border-b border-gray-700 pb-4">
Project Information
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8">
{renderSelectField(
"services",
"Select Services",
"Choose primary service",
[
{
value: "mobile-app",
label: "Mobile App Development",
},
{ value: "web-development", label: "Web Development" },
{ value: "ai-ml", label: "AI & Machine Learning" },
{ value: "ui-ux", label: "UI/UX Design" },
{ value: "enterprise", label: "Enterprise Solutions" },
{ value: "consultation", label: "Consultation" },
]
)}
{renderSelectField(
"budget",
"Budget Range",
"Select budget range",
[
{ value: "under-25k", label: "Under $25,000" },
{ value: "25k-50k", label: "$25,000 - $50,000" },
{ value: "50k-100k", label: "$50,000 - $100,000" },
{ value: "100k-250k", label: "$100,000 - $250,000" },
{ value: "250k-500k", label: "$250,000 - $500,000" },
{ value: "500k-plus", label: "$500,000+" },
]
)}
</div>
{renderTextarea(
"projectDescription",
"Project Description",
"Tell us about your project vision, goals, and key requirements..."
)}
{renderRadioGroup(
"developmentStage",
"Current Development Stage",
[
{ value: "idea", label: "Idea" },
{ value: "designed", label: "Designed Solution" },
{ value: "prototype", label: "Prototype/Spec" },
{ value: "mvp", label: "MVP" },
],
"grid-cols-2 md:grid-cols-4"
)}
</div>
{/* Additional Details Section */}
<div className="space-y-8">
<h3 className="text-xl font-semibold text-white border-b border-gray-700 pb-4">
Additional Details
</h3>
<div className="space-y-4">
<label className="block text-sm font-medium text-white">
Project Attachments
</label>
<div className="border-2 border-dashed border-gray-600/50 rounded-xl p-8 text-center bg-gray-800/10">
<Upload className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-300 mb-2 font-medium">
Upload Additional Files
</p>
<p className="text-sm text-gray-500 mb-6">
Attach wireframes, designs, or requirements. Max file
size: 10MB
</p>
<input
type="file"
multiple
accept=".pdf,.doc,.docx,.jpg,.jpeg,.png,.fig"
onChange={handleFileUpload}
className="hidden"
id="file-upload"
/>
<Button
type="button"
variant="outline"
onClick={() =>
document.getElementById("file-upload")?.click()
}
className="border-gray-600 text-white hover:bg-gray-800 h-12"
>
<Upload className="w-4 h-4 mr-2" />
Choose Files
</Button>
</div>
{attachedFiles.length > 0 && (
<div className="space-y-3">
{attachedFiles.map((file, index) => (
<div
key={index}
className="flex items-center justify-between bg-gray-800/30 p-4 rounded-lg border border-gray-700/30"
>
<div className="flex items-center space-x-3">
<FileText className="w-5 h-5 text-[#E5195E]" />
<span className="text-white font-medium">
{file.name}
</span>
<span className="text-gray-400 text-sm">
({(file.size / 1024 / 1024).toFixed(1)} MB)
</span>
</div>
<Button
type="button"
variant="ghost"
size="sm"
onClick={() => removeFile(index)}
className="text-red-400 hover:text-red-300 hover:bg-red-900/20"
>
Remove
</Button>
</div>
))}
</div>
)}
</div>
{renderRadioGroup(
"timeline",
"Expected Start Timeline",
[
{ value: "1-month", label: "1 Month" },
{ value: "6-month", label: "6 Months" },
{ value: "1-year", label: "1 Year" },
{ value: "1.5-year", label: "1.5 Years" },
{ value: "2-plus-year", label: "2+ Years" },
],
"grid-cols-2 md:grid-cols-5"
)}
</div>
{/* Legal & Agreements */}
<div className="space-y-6">
{renderCheckbox(
"ndaRequired",
"I want to protect my data by signing an NDA (Non-Disclosure Agreement)"
)}
{renderCheckbox(
"agreeTerms",
<>
I agree to the{" "}
<span className="text-[#E5195E] underline">
terms & conditions
</span>{" "}
and{" "}
<span className="text-[#E5195E] underline">
privacy policy
</span>{" "}
*
</>,
true
)}
</div>
{/* Security Verification Section */}
<div className="space-y-6">
<h3 className="text-xl font-semibold text-white border-b border-gray-700 pb-4 flex items-center gap-3">
<Shield className="w-5 h-5 text-[#E5195E]" />
Security Verification
</h3>
<div className="space-y-4">
<div className="flex items-start space-x-4 p-4 bg-gray-800/20 rounded-lg border border-gray-700/30">
<Checkbox
id="human-verification"
checked={isHumanVerified}
onCheckedChange={handleVerificationCheckbox}
className="mt-1"
/>
<label
htmlFor="human-verification"
className="text-gray-300 leading-relaxed cursor-pointer"
>
I'm not a robot
</label>
</div>
{isHumanVerified && (
<div className="flex items-center justify-center gap-2 text-green-400 text-sm p-3 bg-green-900/20 rounded-lg">
<CheckCircle className="w-4 h-4" />
<span>Verification successful</span>
</div>
)}
{errors.humanVerification && (
<p className="text-red-400 text-sm mt-1">
{errors.humanVerification}
</p>
)}
</div>
</div>
{/* Submit Button */}
<div className="pt-8">
<ShimmerButton
type="submit"
className="w-full py-6 text-xl rounded-2xl shadow-lg hover:shadow-xl"
disabled={!formData.agreeTerms || !isHumanVerified || isLoading}
>
<div className="inline-flex items-center justify-center gap-3">
{isLoading ? (
<span>Submitting...</span>
) : (
<>
<Rocket className="w-6 h-6 flex-shrink-0" />
<span>Submit Project Request</span>
<ArrowRight className="w-5 h-5" />
</>
)}
</div>
</ShimmerButton>
{(!formData.agreeTerms || !isHumanVerified) && (
<p className="text-center text-sm text-gray-400 mt-3">
{!formData.agreeTerms && !isHumanVerified
? "Please agree to terms and complete verification to submit"
: !formData.agreeTerms
? "Please agree to terms and conditions to submit"
: "Please complete the security verification to submit"}
</p>
)}
</div>
</form>
</CardContent>
</Card>
</motion.div>
</div>
{/* Math Verification Popup */}
<MathVerificationPopup
isOpen={showVerificationPopup}
onVerify={handleHumanVerification}
onClose={() => setShowVerificationPopup(false)}
/>
</section>
);
};