Files
Pennyzo-Frontend/src/components/WaitlistForm.tsx
ectasy663 ee87f004ed Initial commit: Complete Pennyzo Frontend with waitlist form implementation
- Added React TypeScript frontend with Vite build system
- Implemented luxury-themed UI with custom Tailwind colors
- Created comprehensive waitlist form with conditional logic:
  * Personal information (name, email)
  * Company details (name, type, GST registration)
  * Conditional CIN/PAN fields based on company type and GST status
  * Industry selection and goals questionnaire
- Added form validation and error handling
- Integrated waitlist form with hero section and live counter
- Implemented responsive design with elegant animations
- Added proper gitignore for frontend project
2025-09-17 20:16:59 +05:30

456 lines
22 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 } from 'react';
interface WaitlistFormProps {
isVisible: boolean;
onClose: () => void;
onBack: () => void;
}
interface FormData {
firstName: string;
lastName: string;
email: string;
companyName: string;
companyType: string;
gstRegistered: string;
cin: string;
panNumber: string;
industry: string;
goals: string;
}
const WaitlistForm: React.FC<WaitlistFormProps> = ({ isVisible, onClose, onBack }) => {
const [formData, setFormData] = useState<FormData>({
firstName: '',
lastName: '',
email: '',
companyName: '',
companyType: '',
gstRegistered: '',
cin: '',
panNumber: '',
industry: '',
goals: ''
});
const [isLoading, setIsLoading] = useState(false);
const [errors, setErrors] = useState<Partial<FormData>>({});
const companyTypes = [
'Pvt Ltd',
'LLP',
'Partnership',
'Sole Proprietorship',
'One Person Company (OPC)',
'Public Limited Company',
'Other'
];
const industries = [
'Information Technology',
'Finance & Banking',
'E-commerce',
'Manufacturing',
'Healthcare',
'Education',
'Real Estate',
'Retail',
'Consulting',
'Marketing & Advertising',
'Food & Beverage',
'Transportation',
'Other'
];
const handleInputChange = (field: keyof FormData, value: string) => {
setFormData(prev => {
const newData = { ...prev, [field]: value };
// Clear conditional fields when company type changes
if (field === 'companyType') {
// Clear both fields when company type changes
newData.cin = '';
newData.panNumber = '';
}
// Clear conditional fields when GST registration changes to "yes"
if (field === 'gstRegistered' && value === 'yes') {
newData.cin = '';
newData.panNumber = '';
}
return newData;
});
// Clear error when user starts typing
if (errors[field]) {
setErrors(prev => ({ ...prev, [field]: '' }));
}
};
const validateForm = (): boolean => {
const newErrors: Partial<FormData> = {};
if (!formData.firstName.trim()) newErrors.firstName = 'First name is required';
if (!formData.lastName.trim()) newErrors.lastName = 'Last name is required';
if (!formData.email.trim()) {
newErrors.email = 'Email is required';
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
newErrors.email = 'Please enter a valid email';
}
if (!formData.companyName.trim()) newErrors.companyName = 'Company name is required';
if (!formData.companyType) newErrors.companyType = 'Company type is required';
if (!formData.gstRegistered) newErrors.gstRegistered = 'GST registration status is required';
// Conditional validation - only when GST is NOT registered
if (formData.gstRegistered === 'no') {
if (['Pvt Ltd', 'LLP'].includes(formData.companyType)) {
// For Private Limited and LLP - CIN is required only when GST is not registered
if (!formData.cin.trim()) newErrors.cin = 'CIN is required for Private Limited and LLP companies';
} else if (['Partnership', 'Sole Proprietorship', 'One Person Company (OPC)', 'Other'].includes(formData.companyType)) {
// For Partnership, Proprietorship, Solo/Individual - PAN is required only when GST is not registered
if (!formData.panNumber.trim()) newErrors.panNumber = 'PAN Number is required for this company type';
}
}
if (!formData.industry) newErrors.industry = 'Industry is required';
if (!formData.goals.trim()) newErrors.goals = 'Please tell us about your goals';
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) return;
setIsLoading(true);
// Simulate form submission
await new Promise(resolve => setTimeout(resolve, 2000));
setIsLoading(false);
console.log('Waitlist form submitted:', formData);
// Handle success - maybe show success message or close form
onClose();
};
if (!isVisible) return null;
return (
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-deep-forest/80 backdrop-blur-sm">
<div className="relative max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto">
{/* Close Button */}
<button
onClick={onClose}
className="absolute -top-2 -right-2 w-8 h-8 bg-luxury-gold hover:bg-luxury-gold/90 text-deep-forest rounded-full flex items-center justify-center font-bold z-10 transition-all duration-200 shadow-lg"
>
×
</button>
{/* Back Button */}
<button
onClick={onBack}
className="absolute -top-2 -left-2 w-8 h-8 bg-luxury-gold/20 hover:bg-luxury-gold/30 text-luxury-gold border border-luxury-gold/30 rounded-full flex items-center justify-center font-bold z-10 transition-all duration-200 shadow-lg"
>
←
</button>
{/* Form Container */}
<div className="bg-gradient-to-br from-deep-forest/95 to-rich-brown/90 backdrop-blur-2xl border border-luxury-gold/30 rounded-2xl shadow-2xl overflow-hidden">
{/* Header */}
<div className="bg-gradient-to-r from-luxury-gold/10 to-luxury-gold/5 px-8 py-6 border-b border-luxury-gold/20">
<div className="text-center">
<img
src="/assets/Dark-Icon-cropped.svg"
alt="Pennyzo Icon"
className="w-16 h-16 mx-auto mb-4"
/>
<h2 className="text-luxury-gold font-luxury-serif text-2xl font-bold mb-2">
Join Our Exclusive Waitlist
</h2>
<p className="text-cream/70 font-elegant-sans text-sm">
Complete your application to secure your spot
</p>
</div>
</div>
{/* Form */}
<div className="p-8">
<form onSubmit={handleSubmit} className="space-y-6">
{/* Personal Information Section */}
<div>
<h3 className="text-luxury-gold font-elegant-sans text-lg font-semibold mb-4 flex items-center">
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
</svg>
Personal Information
</h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{/* First Name */}
<div>
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
First Name <span className="text-red-400">*</span>
</label>
<input
type="text"
value={formData.firstName}
onChange={(e) => handleInputChange('firstName', e.target.value)}
className={`w-full bg-deep-forest/50 border ${errors.firstName ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
placeholder="Enter your first name"
/>
{errors.firstName && (
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.firstName}</p>
)}
</div>
{/* Last Name */}
<div>
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
Last Name <span className="text-red-400">*</span>
</label>
<input
type="text"
value={formData.lastName}
onChange={(e) => handleInputChange('lastName', e.target.value)}
className={`w-full bg-deep-forest/50 border ${errors.lastName ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
placeholder="Enter your last name"
/>
{errors.lastName && (
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.lastName}</p>
)}
</div>
</div>
{/* Email */}
<div className="mt-4">
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
Email Address <span className="text-red-400">*</span>
</label>
<input
type="email"
value={formData.email}
onChange={(e) => handleInputChange('email', e.target.value)}
className={`w-full bg-deep-forest/50 border ${errors.email ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
placeholder="Enter your email address"
/>
{errors.email && (
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.email}</p>
)}
</div>
</div>
{/* Company Information Section */}
<div>
<h3 className="text-luxury-gold font-elegant-sans text-lg font-semibold mb-4 flex items-center">
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4" />
</svg>
Company Information
</h3>
{/* Company Name */}
<div className="mb-4">
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
Company Name <span className="text-red-400">*</span>
</label>
<input
type="text"
value={formData.companyName}
onChange={(e) => handleInputChange('companyName', e.target.value)}
className={`w-full bg-deep-forest/50 border ${errors.companyName ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
placeholder="Enter your company name"
/>
{errors.companyName && (
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.companyName}</p>
)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
{/* Company Type */}
<div>
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
Company Type <span className="text-red-400">*</span>
</label>
<select
value={formData.companyType}
onChange={(e) => handleInputChange('companyType', e.target.value)}
className={`w-full bg-deep-forest/50 border ${errors.companyType ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
>
<option value="">Select company type</option>
{companyTypes.map(type => (
<option key={type} value={type} className="bg-deep-forest text-cream">
{type}
</option>
))}
</select>
{errors.companyType && (
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.companyType}</p>
)}
</div>
{/* GST Registered */}
<div>
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
GST Registered <span className="text-red-400">*</span>
</label>
<div className="flex space-x-4 pt-2">
<label className="flex items-center space-x-2 cursor-pointer">
<input
type="radio"
name="gstRegistered"
value="yes"
checked={formData.gstRegistered === 'yes'}
onChange={(e) => handleInputChange('gstRegistered', e.target.value)}
className="w-4 h-4 text-luxury-gold bg-deep-forest/50 border-luxury-gold/20 focus:ring-luxury-gold/50 focus:ring-2"
/>
<span className="text-cream/90 font-elegant-sans text-sm">Yes</span>
</label>
<label className="flex items-center space-x-2 cursor-pointer">
<input
type="radio"
name="gstRegistered"
value="no"
checked={formData.gstRegistered === 'no'}
onChange={(e) => handleInputChange('gstRegistered', e.target.value)}
className="w-4 h-4 text-luxury-gold bg-deep-forest/50 border-luxury-gold/20 focus:ring-luxury-gold/50 focus:ring-2"
/>
<span className="text-cream/90 font-elegant-sans text-sm">No</span>
</label>
</div>
{errors.gstRegistered && (
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.gstRegistered}</p>
)}
</div>
</div>
{/* Conditional CIN Field - shows when company type is Pvt Ltd or LLP AND GST is NOT registered */}
{formData.gstRegistered === 'no' && ['Pvt Ltd', 'LLP'].includes(formData.companyType) && (
<div className="mb-4">
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
CIN (Company Identification Number) <span className="text-red-400">*</span>
</label>
<input
type="text"
value={formData.cin}
onChange={(e) => handleInputChange('cin', e.target.value.toUpperCase())}
className={`w-full bg-deep-forest/50 border ${errors.cin ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
placeholder="e.g., L12345MH2020PLC123456"
maxLength={21}
/>
{errors.cin && (
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.cin}</p>
)}
<p className="text-cream/50 text-xs mt-1 font-elegant-sans">
21-digit Corporate Identity Number (required when GST not registered)
</p>
</div>
)}
{/* Conditional PAN Number Field - shows for other company types AND GST is NOT registered */}
{formData.gstRegistered === 'no' && ['Partnership', 'Sole Proprietorship', 'One Person Company (OPC)', 'Other'].includes(formData.companyType) && (
<div className="mb-4">
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
PAN Number <span className="text-red-400">*</span>
</label>
<input
type="text"
value={formData.panNumber}
onChange={(e) => handleInputChange('panNumber', e.target.value.toUpperCase())}
className={`w-full bg-deep-forest/50 border ${errors.panNumber ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
placeholder="e.g., ABCDE1234F"
maxLength={10}
/>
{errors.panNumber && (
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.panNumber}</p>
)}
<p className="text-cream/50 text-xs mt-1 font-elegant-sans">
10-digit Permanent Account Number (required when GST not registered)
</p>
</div>
)}
{/* Industry */}
<div>
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
Industry <span className="text-red-400">*</span>
</label>
<select
value={formData.industry}
onChange={(e) => handleInputChange('industry', e.target.value)}
className={`w-full bg-deep-forest/50 border ${errors.industry ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans`}
>
<option value="">Select your industry</option>
{industries.map(industry => (
<option key={industry} value={industry} className="bg-deep-forest text-cream">
{industry}
</option>
))}
</select>
{errors.industry && (
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.industry}</p>
)}
</div>
</div>
{/* Qualifying Question Section */}
<div>
<h3 className="text-luxury-gold font-elegant-sans text-lg font-semibold mb-4 flex items-center">
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z" />
</svg>
Tell Us About Your Goals
</h3>
<div>
<label className="block text-cream/90 font-elegant-sans text-sm font-medium mb-2">
What do you want to achieve with Pennyzo? <span className="text-red-400">*</span>
</label>
<textarea
value={formData.goals}
onChange={(e) => handleInputChange('goals', e.target.value)}
rows={4}
className={`w-full bg-deep-forest/50 border ${errors.goals ? 'border-red-400' : 'border-luxury-gold/20'} rounded-lg px-4 py-3 text-cream placeholder-cream/40 focus:outline-none focus:ring-2 focus:ring-luxury-gold/50 focus:border-luxury-gold/50 transition-all duration-300 font-elegant-sans resize-none`}
placeholder="Tell us about your business goals, challenges you're facing, or what you hope to achieve with our platform..."
/>
{errors.goals && (
<p className="text-red-400 text-xs mt-1 font-elegant-sans">{errors.goals}</p>
)}
</div>
</div>
{/* Submit Button */}
<div className="pt-4">
<button
type="submit"
disabled={isLoading}
className="w-full bg-gradient-to-r from-luxury-gold to-luxury-gold/90 hover:from-luxury-gold/90 hover:to-luxury-gold text-deep-forest font-elegant-sans font-semibold py-4 px-4 rounded-xl transition-all duration-300 transform hover:scale-105 hover:shadow-lg disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:scale-100 relative overflow-hidden group"
>
{isLoading ? (
<div className="flex items-center justify-center space-x-2">
<div className="w-5 h-5 border-2 border-deep-forest border-t-transparent rounded-full animate-spin"></div>
<span>Submitting Application...</span>
</div>
) : (
<>
<span className="relative z-10">Submit Application</span>
<div className="absolute inset-0 bg-cream transform scale-x-0 group-hover:scale-x-100 transition-transform origin-left duration-300"></div>
</>
)}
</button>
</div>
{/* Privacy Notice */}
<div className="text-center text-cream/50 font-elegant-sans text-xs">
By submitting this form, you agree to our terms of service and privacy policy.
Your information will be kept confidential and used only for waitlist management.
</div>
</form>
</div>
</div>
</div>
</div>
);
};
export default WaitlistForm;